Oracle c programming linux

Oracle Call Interface: как написать клиентское приложение на Си

Хабр, привет! Вообще-то я не настоящий сварщик программист, я системный администратор и администратор БД. Поэтому, когда несколько лет назад передо мной встала задача написать небольшое клиентское приложение для автоматизации рутинных процедур, я потратил немало времени, чтобы разобраться в этом вопросе. Если вдруг у вас примерно такая же ситуация, надеюсь, эта статья сэкономит ваше время и силы.

Что нужно?

Определившись с требованиями получаем следующее.

Желательно для бинарника:

  • Небольшой размер исполняемого файла.
  • Корректная обработка случаев, когда OCI-библиотека недоступна

Необходимо для бинарника:

  • Работать утилита должна на любом компьютере с ОС Windows выше Windows 7 или Windows Server 2008 без установки каких-либо фреймворков и Runtime Environment. Должен быть установлен только какой-то из продуктов Oracle, включающий OCI-библиотеку.

Очень желательно для исходников:

  • Чтобы готовый проект можно было без проблем скомпилировать на другом компьютере без установки каких-либо библиотек. Так как у нас есть похожий legacy проект, которую лет 20 назад писал давно ушедший сотрудник. Его рабочую станцию мы храним в виде образа ВМ, так как подготовить окружение на новой рабочей станции — задача крайне нетривиальная.

Какую технологию использовать?

Oracle предлагает следующие варианты:

  • Pro*C/C++ — Предкомпилятор Oracle. Инструмент программирования, который позволяет встраивать операторы SQL в хост-программу высокого уровня. Предварительный компилятор принимает основную программу в качестве входных данных, преобразует встроенные инструкции SQL в стандартные вызовы библиотеки Oracle во время выполнения и создает исходную программу, которую вы можете скомпилировать, связать и выполнить. Разобраться с этим у меня не получилось, поэтому ничего к этому описанию я добавить не могу
  • C++ Call Interface (OCCI) — API, который предоставляет приложениям C++ доступ к данным в базе данных Oracle. OCCI позволяет программистам на C++ использовать весь спектр операций с базами данных Oracle, включая обработку инструкций SQL и манипулирование объектами. Так как я С++ в реальных проектах не использовал, этот вариант мне явно не подходил
  • Oracle Call Interface (OCI) — API, который позволяет создавать приложения, использующие вызовы функций для доступа к базе данных Oracle и управления всеми этапами выполнения инструкций SQL. OCI поддерживает типы данных, соглашения о вызовах, синтаксис и семантику C и C++. Он предоставляет библиотеку стандартных функций доступа к базе данных и поиска в виде библиотеки динамической среды выполнения (библиотеки OCI), которая может быть связана в приложении во время выполнения. Также в описании указано, что все оракловые утилиты (типа sqlplus, exp, imp и прочие) написаны именно с использованием OCI. Что же еще нужно?
  • Oracle Database Programming Interface for C (ODPI-C) — это C-библиотека с открытым исходным кодом, которая упрощает использование общих функций интерфейса вызовов Oracle (OCI) для драйверов баз данных Oracle и пользовательских приложений. DPIC находится поверх OCI и требует клиентских библиотек Oracle. Проект лежит на Гитхабе. Про этот вариант я прочитал только когда писал эту статью. Но, посмотрев примеры программ, я нисколько не пожалел, что выбрал OCI. Может, это вопрос привычки и опыта, но простой эта библиотека мне не показалась.
Читайте также:  Загрузка модуля в линукс

Пишем простое приложение

Подключение библиотеки

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

Подключение библиотеки и получений адресов функций

//подкллючаем библиотеку в Windows hOCIDll = LoadLibraryW(L"oci.dll"); //или в Линукс ocimodule = dlopen("libclntsh.so", RTLD_LAZY); //Определяем тип для указателя функции typedef sword(*pOCIEnvCreate)(OCIEnv **hOraEnvp, ub4 mode, const void *ctxp, const void *(*malocfp) (void *ctxp, size_t size), const void *(*ralocfp) (void *ctxp, void *memptr, size_t newsize), const void(*mfreefp) (void *ctxp, void *memptr), size_t xtramemsz, void **usrmempp); //выделяем переменную для адреса функции pOCIEnvCreate OCIEnvCreate; //Получаем адрес функции на Windows OCIEnvCreate = (pOCIEnvCreate)GetProcAddress(hOCIDll, "OCIEnvCreate"); // Или на Linux OCIEnvCreate = (pOCIEnvCreate)dlsym(ocilib,"OCIEnvCreate"); 

Таком образом нужно получить адреса всех функций, которые мы будем использовать в нашей программе.

Определения функций можно найти либо на https://docs.oracle.com в Call Interface Programmer’s Guide для вашей версии библиотеки либо в заголовочном файле %ORACLE_HOME%\OCI\include\ociap.h

Инициализация окружения

Далее нам нужно инициализировать структуры OCIEnv (хендл окружения) и OCIError (хендл для обработки ошибок).

OCIEnv *hOraEnv = NULL; OCIError *hOraErr = NULL; OCIEnvCreate((OCIEnv **)&hOraEnv, (ub4)OCI_DEFAULT | OCI_OBJECT, (const void *)0, (const void * (*)(void *, size_t))0, (const void * (*)(void *, void *, size_t))0, (const void(*)(void *, void *))0, (size_t)0, (void **)0)); OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraErr, OCI_HTYPE_ERROR, (size_t)0, (void **)0)); 

Создание сессии

Для каждой сессии нам нужно инициализировать хендл OCISvcCtx, именно он используется для выполнения sql-выражений. Я, руководствуясь демонстрационными примерами от Oracle, создавал сессии так:

OCIServer *hOraServer = NULL; OCISvcCtx *hOraSvcCtxOCI = NULL; OCISession *hOraSession = NULL; //Аллоцируем хендл OCIServer OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraServer, OCI_HTYPE_SERVER, (size_t)0, (dvoid **)0); char *dbconnectstring = "servername:1521/ORCL"; //Подключаемся к серверу OCIServerAttach(hOraServer, hOraErr, (const OraText *)dbconnectstring, (sb4)strlen(dbconnectstring), (ub4)OCI_DEFAULT); //Аллоцируем хендл сервисного контекста OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraSvcCtx, OCI_HTYPE_SVCCTX, (size_t)0, (dvoid **)0); //Помещаем хендл сервера в сервисного контекста OCIAttrSet((void *)hOraSvcCtx, OCI_HTYPE_SVCCTX, (void *)hOraServer, (ub4)0, OCI_ATTR_SERVER, (OCIError *)hOraErr)); //Аллоцируем хендл для сессии OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraSession, (ub4)OCI_HTYPE_SESSION, (size_t)0, (void **)0); char * username = "SCOTT"; //Помещаем имя пользователя в хендл сессии OCIAttrSet((void *)hOraSession, (ub4)OCI_HTYPE_SESSION, (void *)username, (ub4)strlen(username), (ub4)OCI_ATTR_USERNAME, hOraErr); char *password = "tiger"; //Помещаем пароль в хендл сессии OCIAttrSet((void *)hOraSession, (ub4)OCI_HTYPE_SESSION, (void *)password, (ub4)strlen(password), (ub4)OCI_ATTR_PASSWORD, hOraErr); //флаг для указания, является ли пользователь sysdba bool assysdba = 1; //Начинаем сессию OCISessionBegin(hOraSvcCtx, hOraErr, hOraSession, OCI_CRED_RDBMS, (ub4)(OCI_DEFAULT | (assysdba ? OCI_SYSDBA : 0))); //Помещаем сессию в сервисный контекст OCIAttrSet((void *)hOraSvcCtx, (ub4)OCI_HTYPE_SVCCTX, (void *)hOraSession, (ub4)0, (ub4)OCI_ATTR_SESSION, hOraErr); 

Уже в процессе написания статьи, я увидел, что для этих же целей можно использовать функцию OCILogon2. Судя по всему, это намного проще и удобнее. А главное, нужно меньше писать кода. Но мы уже оставим все, как есть.

Читайте также:  Узнать версию gcc linux

Вставляем данные

Пример функции для загрузки данных в базу:

char * insert_statement = "INSERT INTO simple_table\ (id, textfield)\ VALUES\ (:id, :string)"; sword status; int id; char stringBuffer[100]; OCIStmt *hOraPlsqlStatement = NULL; //Аллоцируем хендл для sql-выражения OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraPlsqlStatement, OCI_HTYPE_STMT, (size_t)0, (void **)0); //Подготавливаем его OCIStmtPrepare(hOraPlsqlStatement, hOraErr, (const OraText *)insert_statement, (ub4)strlen(insert_statement), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); //Биндим наши переменные id и stringBuffer в sql-выражение OCIBind *bnd1p = NULL; OCIBind *bnd2p = NULL; OCIBindByName(hOraPlsqlStatement, &bnd1p, hOraErr, (text *)":id", -1, (void *)&id, (sb4)sizeof(id), SQLT_INT, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT); OCIBindByName(hOraPlsqlStatement, &bnd2p, hOraErr, (text *)":string", -1, (void *)stringBuffer, (sb4)(sizeof(stringBuffer)), SQLT_STR, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT); //вставляем данные в цикле for (id = 1; id < 10; id++) < sprintf(stringBuffer, "This is the %d string", id); status = OCIStmtExecute(hOraSvcCtx, hOraPlsqlStatement, hOraErr, (ub4)1, (ub4)0, (CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT); if (status != OCI_SUCCESS && status != OCI_SUCCESS_WITH_INFO) < checkerr(hOraErr, status); OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); return FALSE; >> //освобождаем хендл OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); 

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

Получаем данные

Код для получения данных из базы:

char * select_statement = "select id, textfield from simple_table order by id"; sword status; int id; char stringBuffer[100]; OCIStmt *hOraPlsqlStatement = NULL; OCIHandleAlloc((const void *)hOraEnv, (void **)&hOraPlsqlStatement, OCI_HTYPE_STMT, (size_t)0, (void **)0); OCIStmtPrepare(hOraPlsqlStatement, hOraErr, (const OraText *)select_statement, (ub4)strlen(select_statement), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIDefine *OraIdDefine = NULL; //Привязываем переменную id к первому полю в запросе OCIDefineByPos(hOraPlsqlStatement, &OraIdDefine, hOraErr, 1, (void *)&id, (sword)sizeof(id), SQLT_INT, (void *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT); OCIDefine *OraStringDefine = NULL; //Привязываем stringBuffer ко второму полю в запросе OCIDefineByPos(hOraPlsqlStatement, &OraStringDefine, hOraErr, 2, (void *)stringBuffer, (sword)sizeof(stringBuffer), SQLT_STR, (void *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT); //Выполняем запрос, не получая никаких данных status = OCIStmtExecute(hOraSvcCtx, hOraPlsqlStatement, hOraErr, (ub4)0, (ub4)0, (CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT); if (status != OCI_SUCCESS && status != OCI_SUCCESS_WITH_INFO) < checkerr(hOraErr, status); OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); return FALSE; >printf("id | textfield\n"); //Получаем данные в цикле while ((status = OCIStmtFetch2(hOraPlsqlStatement, hOraErr, 1, OCI_DEFAULT, 0, OCI_DEFAULT)) == OCI_SUCCESS || status == OCI_SUCCESS_WITH_INFO) < printf("%d | %s\n", id, stringBuffer); >OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); 

При получении данных поступаем аналогично, привязываем переменные с запрошенными полями в селекте, выполняем выражение и затем в цикле одну за другой получаем строки

Читайте также:  Виртуальный выделенный сервер linux

Закрываем сессию

После нужных операции нужно закрыть сессию:

OCISessionEnd(hOraSvcCtx, hOraErr, hOraSession, OCI_DEFAULT); OCIHandleFree(hOraSession, OCI_HTYPE_SESSION); OCIHandleFree(hOraSvcCtx, OCI_HTYPE_SVCCTX); OCIServerDetach(hOraServer, hOraErr, (ub4)OCI_DEFAULT); OCIHandleFree(hOraServer, OCI_HTYPE_SERVER);

Закрываем окружение

После того, как взаимодействие с базой больше не нужно, закрываем окружение:

OCIHandleFree(hOraErr, OCI_HTYPE_ERROR); OCIHandleFree(hOraEnv, OCI_HTYPE_ENV); OCITerminate(OCI_DEFAULT);

Итоги работы

Вот ссылки на проекты на гитхабе для Windows(Visual Studio) и Linux(NetBeans)

О файлах в проекте

Заголовочные файлы oci.h, ocidem.h, ocidfn.h, ocikpr.h, oratypes.h, orl.h взяты из каталога %ORACLE_HOME%\OCI\include (Может, я их немного модифицировал, чтобы не было неразрешенных зависимостей, но этого я уже точно не помню)
ocipfndfn.h — этот файл с определениями типов указателей на функции составлен мной, тут те OCI функции, которые я использовал в своих проектах.
OraFunction.c — Основной файл проекта, тут определения функций для работы с БД.
OraFunction.h — заголовочный файл с объявлениями функции.
ParseCmdLine.c — функция для парсинга командной строки, для получения логина, пароля и строки подключения к базе. Параметры передаются в утилиту стандартным для Оракловых утилит форматом login/pass@dbconnect.
main.c — функция main.

Что утилита делает

Утилита подключается к базе данных, создает таблицу simple_table, заполняет ее данными, получает из нее данные и удаляет таблицу.

Как скомпилировать

Как и было запланировано с самого начала, для компиляции утилиты не требуется никаких дополнительных файлов и библиотек. Для работы, само собой, требуется установленный Oracle Client либо Instant Oracle Cllient, главное, чтобы была доступна OCI-библиотека.

Источник

Оцените статью
Adblock
detector