Работа с DB Oracle из Xcode

в 9:42, , рубрики: Cocoa, database, mac os x, oracle, xcode, метки: , , ,

Я хотел научиться работать с oracle из xcode, так как не обнаружил готового фреймворка от apple для работы с базой данных oracle напрямую.
Для начала, я попробовал создать тестовый проект и подключить к нему instantclient от oracle. Также я использовал тестовый пример от Oracle ( cdemo81.c ), включил его код в проект на xcode и протестировал работу. Да, все работает, хотя пришлось немножко побить в шаманский бубен от google. Тем не менее, использовать напрямую oci библиотеку тяжело, так как нужно реализовать интерфейс, а это похоже на изобретение велосипеда.
Тогда я решил попробовать использовать кросс-платформенную библиотеку ocilib ( http://orclib.sourceforge.net )
Далее в тексте пошаговая инструкция о том, как сделать тестовый проект на cocoa и использовать эту библиотеку. Цель тестового проекта — подключив библиотеку получить данные с сервера oracle.

1) Качаем саму библиотеку http://orclib.sourceforge.net/download/
2) Качаем instantclient от oracle для macosx http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html, Меня в данном случае интересует Version 11.2.0.3.0 (64-bit), Instant Client Package — Basic: All files required to run OCI, OCCI, and JDBC-OCI applications ( http://download.oracle.com/otn/mac/instantclient/11203/instantclient-basic-macos.x64-11.2.0.3.0.zip ) Для скачивания потребуется регистрация, но она бесплатная. Также потребуются header файлы для компиляции ocilib, поэтому качаем «Instant Client Package — SDK: Additional header files and an example makefile for developing Oracle applications with Instant Client», в моем случае вот этот — instantclient-sdk-macos.x64-11.2.0.3.0.zip ( http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html )
3) Распакуем instantclient в папку, например /usr/local/lib/instantclient_11_2
4) Также, распакуем SDK c header файлами во внутрь папки с instantclient, например вот так — /usr/local/lib/instantclient_11_2/sdk
5) Теперь, необходимо создать симлинк для библиотеки в папке instantclient, в консоли переходим в папку /usr/local/lib/instantclient_11_2, далее делаем симлинк — «ln -s libclntsh.dylib.11.1 libclntsh.dylib»
6) Далее, распаковываем архив с библиотекой ocilib, например в папку /Users/username/Downloads/ocilib-3.12.1
7) Открываем терминал и переходим в папку с ocilib, например так — cd /Users/username/Downloads/ocilib-3.12.1
8) В консоли последовательно cобираем библиотеку

./configure --with-oracle-lib-path=/usr/local/lib/instantclient_11_2/ —with-oracle-headers-path=/usr/local/lib/instantclient_11_2/sdk/include
make
sudo make install

После этого, библиотека должа быть установлена в папке /usr/local/lib, проверьте наличие файлов в папке, таких как libocilib*
Первый этап подготовки библиотек выполнен. Теперь попробуем создать тестовый проект в XCode

1) Создаем в Xcode новый проект (OS X -> Cocoa Application )
По умолчанию в Xcode ( у меня установлена версия 5.1 ) создается пустой проект с MainMenu.xib в котором есть пустое view.
2) Для начала добавим в проект header файл библиотеки ocilib. Открываем папку /Users/username/Downloads/ocilib-3.12.1/include и копируем оттуда файл ocilib.h в папку нашего проекта для XCode, и добавляем этот файл в наш проект (в xcode, внутри папки проекта делаем file->add files to «project name» и выбираем наш ocilib.h
3) Далее нужно добавить в проект ссылки линковки для библиотек, для этого открываем Build Settings проекта xcode, ищем Other Linker Flags и добавляем туда два параметра — «-locilib» и «-lclntsh» — первый, ссылка на библиотеку ocilib, второй на instantclient. Если сейчас попробовать скомпилировать проект, получим ошибку — «ld: library not found for -lclntsh». Дело в том, что xcode при компиляции не знает, где именно лежит instantclient oracle, нужно указать к нему путь.
4) Укажем путь к библиотекам — для этого открываем Build Settings проекта xcode, ищем «Library Search Path» и добавляем путь к instantclient и ocilib, давим два параметра «/usr/local/lib/instantclient_11_2» и «/usr/local/lib»

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

1) В AppDelegate.h пропишем #include "ocilib.h"
2) В AppDelegate.m изменим код

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    
    OCI_Connection* cn;
    OCI_Statement* st;
    OCI_Resultset* rs;
    
    OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
    cn = OCI_ConnectionCreate("orcl", "USERNAME", "PASSWORD", OCI_SESSION_DEFAULT);
    st = OCI_StatementCreate(cn);
    
    OCI_ExecuteStmt(st, "select id,name from test_encoding_table");
    rs = OCI_GetResultset(st);
    
    while (OCI_FetchNext(rs)) {
        printf("%i - %sn", OCI_GetInt(rs, 1), OCI_GetString(rs,2));
    }
    OCI_Cleanup(); 
}

Где username, password это имя и пароль для подключения к нашей базе данных, а orcl это имя instance прописанного в tnsnames.ora ( как прописать, об этом чуть позже )
Если сейчас попытаться запустить проект, то получим ошибку, вот такую — «dyld: Library not loaded: /ade/b/2649109290/oracle/rdbms/lib/libclntsh.dylib.11.1
Referenced from: /Users/chepil/Library/Developer/Xcode/DerivedData/test3-hdexygntscvmwahcgsfolpqrkldi/Build/Products/Debug/test3.app/Contents/MacOS/test3
Reason: image not found»

Эта ошибка говорит о том, что при запуске проекта ( да, он скомпилировался нормально ), приложение не знает, где же лежит библиотека на которую мы ссылаемся. Так как одно дело скомпилировать проект, другое дело его исполнение.
Для исправления ошибки либо запустим проект из консоли, установив нужные переменные окружения, либо добавим нужные переменные прямо в xcode.
Для этого щелкнем мышкой на наименовании проекта, в левом верхнем углу окна проекта ( к сожалению, не знаю как сделать это через меню ), и выберем «Edit Scheme…»
Выберем схему запуска ( в моем случае «Run test3.app» ) и откроем вкладку Arguments, в которой пропишем несколько «Environment Variables» — переменные окружения. Нужно понимать, что эти переменные действуют только при запуске проекта из Xcode, и прекратят действовать сразу, как только мы пустим наш app файл в открытое плавание…

3) Добавим переменную окружения DYLD_LIBRARY_PATH со значением /usr/local/lib:/usr/local/lib/instantclient_11_2. Как Вы видите, для добавления нескольких значений, нужно использовать двоеточие. Попробуем запустить проект. Проект запустился, но в консоль ничего не выдалось. Логично, так как во первых, не произошло соединение с базой данных, во вторых, в Вашей базе данных наверняка отсутствует таблица test_encoding_table ( я надеюсь, что Вы догадались заменить запрос на свое значение ). Тем не менее, чего не хватает для соединения с базой данных? Правильно, переменной окружения ORACLE_HOME, в которой должен лежать network/admin/tnsnames.ora

4) создадим папки /Users/username/oracle_home/network/admin
5) создадим файл /Users/username/oracle_home/network/admin/tnsnames.ora
содержимое файла tnsnames.ora:

orcl=
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp) (HOST=orclserver.myaddress.com) (PORT=1521))(CONNECT_DATA=(SERVICE_NAME=orcl)))

В вашем случае подключение может быть другим, настройки подключения к oracle серверу выходят за рамки этой статьи. Подключение выше приведено просто в качестве примера.

6) Пропишем для нашего приложения новую переменную окружения для запуска ( там же, где мы прописывали DYLD_LIBRARY_PATH ). Новая переменная будет иметь имя ORACLE_HOME и иметь значение /Users/username/oracle_home
После этого, при запуске приложения и создании корректного запроса ( к вашей таблице ), Вы начнете получать данные от оракл сервера. Также, можно было прописать значение переменной не ORACLE_HOME, а TNS_ADMIN и указать путь к файлу tnsnames.ora ( оракл администраторы и программисты знают о чем идет речь, не буду разжевывать )

7) В моем случае, таблица test_encoding_table содержит тестовые записи на русском и японском языках, в UTF8 кодировке. При запросе этих данных в xcode, я получил вот такой результат —
1 — ???????????? ???????? ????

Понятно, что это вопросики, а не нужные нам данные, а значит что то работает не так.
Для корректного возврата данных, нужно указать, в какой кодировке мы работаем

8) Пропишем очередную переменную окружения для запуска приложения. Также как и DYLD_LIBRARY_PATH, ORACLE_HOME, создадим переменную NLS_LANG и пропишем ей значение ( например RUSSIAN_CIS.UTF8 )
Запустим проект и получим вывод в консоль проекта —

1 — тестирование русскких имен
2 — 日本語で利用したいのですが

Что и требовалось доказать.

Осталось — мелочи. Давайте теперь выведем результат в NSTextView в главном окне программы. Для этого
Изменим файл MainMenu.xib созданные по умолчанию, положим на window новый NSTextView
Изменим AppDelegate.h

#import <Cocoa/Cocoa.h>
	#include "ocilib.h"
	@interface AppDelegate : NSObject <NSApplicationDelegate> {
    		IBOutlet NSTextView *textView;
	}
	@property (assign) IBOutlet NSWindow *window;
	@property (nonatomic,retain) IBOutlet NSTextView *textView;
	@end

3) Изменим AppDelegate.m

#import "AppDelegate.h"
@implementation AppDelegate
@synthesize textView;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
   NSString *str = @"";
   
    OCI_Connection* cn;
    OCI_Statement* st;
    OCI_Resultset* rs;
    
    OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
    
    cn = OCI_ConnectionCreate("orcl", "username", "password", OCI_SESSION_DEFAULT);
    st = OCI_StatementCreate(cn);
    OCI_ExecuteStmt(st, "select id,name from test_encoding_table");
    rs = OCI_GetResultset(st);
    
    while (OCI_FetchNext(rs))
    {
        printf("%i - %sn", OCI_GetInt(rs, 1), OCI_GetString(rs,2));
        NSString *str1 = [NSString stringWithUTF8String:OCI_GetString(rs,2)];
        str = [NSString stringWithFormat:@"%@n%i: %@",str,OCI_GetInt(rs, 1),str1];
    }
    OCI_Cleanup();
   [textView setString:str];
}
@end

4) на MainMenu.xib откроем AppDelegate и свяжем outlet textView на наш новый NSTextView, который мы положили в наше окошко.

Запустим приложение
Теперь вывод запроса к базе данных дублируется в консоль проекта и в NSTextView.

Последнее уточнение. Если мы захотим запустить программу ( в моем случае test3.app ) из консоли, а не из xcode, то мы должны будем прописать переменные окружения.
Для теста создаем файл test.sh, даем ему права

chmod +x test.sh

и пишем во внутрь что то типа:

#!/bin/sh
export DYLD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/instantclient_11_2
export ORACLE_HOME=/Users/chepil/oracle_home
export NLS_LANG=RUSSIAN_CIS.UTF8

open test3.app

запускаем проект из консоли —

./test3.sh

открывается приложение, в нем окно, делается запрос к базе и результат выводится в textview на экране. Все работает, чего и требовалось доказать.

Напоследок хотелось бы поинтересоваться, возможно существуют другие библитеки для работы с ораклом. Я нахожусь в начале разработки проекта и мне необходимо принять решение о целесообразности работы именно с этой библиотекой. Фактически, библиотека ocilib является оберткой вокруг OCI от оракл, и не хочется изобретать свой велосипед. У кого какой опыт работы с оракл из xcode? Какие замечания есть к тексту статьи?
Спасибо за конструктивную критику!

Автор: chepil

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js