Dans cet article on va voir comment accéder a une base de données Oracle via ODBC en utilisant UnixODBC. ODBC (Open DataBase Connectivity) est un standard qui permet d’accéder a une base de données via une API normé quelque soit son type du moment ou elle se conforme a la cette norme, pour plus d’infos et de théorie. UnixODBC est un projet open source qui implémente les spécifications de la norme ODBC et permet par conséquent l’accès aux sources de données.
Pour installer UnixODBC sous Ubunutu :
sudo apt-get install unixodbc unixodbc-dev
Ou a partir des sources :
wget ftp://ftp.unixodbc.org/pub/unixODBC/unixODBC-2.3.0.tar.gz tar xvfz unixODBC-2.3.0.tar.gz cd unixODBC-2.3.1 ./configure --prefix=/usr/local/UnixODBC make sudo make install
Les commandes suivantes nécessitent les droits root. Créer (si ce n’est pas fait lors de l’install) les deux fichiers suivants :
toc@tocNewServer:~$ cat /etc/odbc.ini [Oracle] Application Attributes = T Attributes = W BatchAutocommitMode = IfAllSuccessful CloseCursor = F DisableDPM = F DisableMTS = T Driver = Oracle EXECSchemaOpt = EXECSyntax = T Failover = T FailoverDelay = 10 FailoverRetryCount = 10 FetchBufferSize = 64000 ForceWCHAR = F Lobs = T Longs = T MetadataIdDefault = F QueryTimeout = T ResultSets = T ServerName = //192.168.1.95:1521/TOCORACL SQLGetData extensions = F Translation DLL = Translation Option = 0 UserID =
ET :
toc@tocNewServer:~$ cat /etc/odbcinst.ini [ODBC] Trace = yes TraceFile = /tmp/odbc.log [Oracle] Description = Oracle ODBC Connection Driver = /usr/lib/oracle/11.2/client/lib/libsqora.so.11.1 Setup = FileUsage = CPTimeout = CPReuse =
UnixODBC vous permet de définir des sources de données (Oracle, Sybase, DB2, Teradata, PostgreSQL, MySQL, …) et de définir un driver pour chacune de ces sources (il s’agit une librairie) qui implémente lui les API permettant l’accès a la source de données. Chaque éditeur de base de données dispose de ces propres drivers.
Pour tester la connexion via ODBC, le package UnixODBC installe un utilitaire qui s’appelle isql (/usr/bin/isql) que vous pouvez utilisez pour requêter sur une base (ou Source de Données dans le jargon ODBC) :
toc@tocNewServer:~$ isql --version unixODBC 2.2.14 toc@tocNewServer:~$ isql -v Oracle hr human +---------------------------------------+ | Connected! | | | | sql-statement | | help [tablename] | | quit | | | +---------------------------------------+ SQL> select OBJECT_NAME from user_objects where OBJECT_TYPE = 'TABLE'; +---------------------------------------------------------------------------------------------------------------------------------+ | OBJECT_NAME | +---------------------------------------------------------------------------------------------------------------------------------+ | REGIONS | | LOCATIONS | | JOB_HISTORY | | JOBS | | EMPLOYEES | | DEPARTMENTS | | COUNTRIES | +---------------------------------------------------------------------------------------------------------------------------------+ SQLRowCount returns -1 7 rows fetched SQL> quit toc@tocNewServer:~$
La syntax est :
isql SOURCE_DE_DONNEES USER PASSWORD
La source de données est le nom que vous avez utilisez dans votre fichier de conf (/etc/odbc.ini) (« Oracle » dans notre cas).
La connexion fonctionne, on peut donc écrire un programme C qui utilise l’API d’UnixODBC pour requeter en base. Voila odbc-demo.c :
/* * odbc-demo.c : ce programme demontre * un simple select dans une base utilisant UnixODBC */ #include <stdio.h> #include <stdlib.h> #include <sql.h> #include <sqlext.h> #define BUFFER_LEN 1024 #define OK 0 #define KO -1 #define TIMEOUT 5 /* Timeout is set to 5 seconds */ #define REC_NUMBER 1 /* Pour la fonction SQLGetDiagRec */ #define SQLSTATE_LEN 7 #define DB_NAME_LEN 128 #define DB_VERSION_LEN 32 #define STRING_REQUEST "SELECT department_id, first_name, last_name, salary FROM HR.employees WHERE department_id is not null" int odbc_connection(char *datasource, char *user, char *pwd) { long status; SQLCHAR state[SQLSTATE_LEN]; /* Pour SQLSTATE code qui designe une erreur ou un warning */ SQLINTEGER odbc_error; SQLSMALLINT error_text_len; SQLCHAR error_text[BUFFER_LEN]; SQLCHAR db_name[DB_NAME_LEN]; /* Nom de la base de donnees */ SQLCHAR db_version[DB_VERSION_LEN]; /* Version de la base de donnes */ SQLHENV environment_handle; /* Pour mettre en place notre environnement */ SQLHDBC connection_handle; /* Pour la connexion a la source de donnees */ SQLHSTMT statment_handle; /* Pour notre requete */ int emp_department_id = 0; SQLCHAR emp_first_name[21]; SQLCHAR emp_last_name[26]; float emp_salary = 0.0; int number_fetched_lines = 0; /* Etape 1. allocation de la memoire pour mettre en place notre environnement ODBC */ status = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &environment_handle); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLAllocHandle: SQL_HANDLE_ENV Failed\n"); return KO; } /* Tous les recents driver ODBC supporte la version 3 d'ODBC */ status = SQLSetEnvAttr(environment_handle, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, SQL_IS_INTEGER); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLSetEnvAttr Failed\n"); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return KO; } /* Etape 2. allocation de la memoire pour le handle de la connexion */ status = SQLAllocHandle(SQL_HANDLE_DBC, environment_handle, &connection_handle); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLAllocHandle: SQL_HANDLE_DBC Failed\n"); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return KO ; } /* Mise en place d'un timeout : temps d'attente d'une connexion */ SQLSetConnectAttr(connection_handle, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) TIMEOUT, 0); /* Etape 3. connexion a la source de donnees "datasource" */ /* On peut aussi utiliser la fonction SQLDriverConnect qui prend plus de parametres */ status = SQLConnect(connection_handle, (SQLCHAR *) datasource, SQL_NTS, (SQLCHAR *) user, SQL_NTS, (SQLCHAR *) pwd, SQL_NTS); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLConnect failed\n"); SQLGetDiagRec(SQL_HANDLE_DBC, connection_handle, REC_NUMBER, state, &odbc_error, error_text, BUFFER_LEN, &error_text_len); fprintf(stderr, "%s (error number : %d)\n", error_text, (int)odbc_error); SQLFreeHandle(SQL_HANDLE_DBC, connection_handle); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return KO; } fprintf(stdout, "Connection to DataSource %s succeeded\n", datasource); /* Le nom et la version de la base de donnees */ SQLGetInfo(connection_handle, SQL_DBMS_NAME, (SQLPOINTER)db_name, sizeof(db_name), NULL); SQLGetInfo(connection_handle, SQL_DBMS_VER, (SQLPOINTER)db_version, sizeof(db_version), NULL); fprintf(stdout, "Database name : %s\n", db_name); fprintf(stdout, "Database version : %s\n", db_version); /* Etape 3. allocation de la memoire pour la requete */ status = SQLAllocHandle(SQL_HANDLE_STMT, connection_handle, &statment_handle); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLAllocHandle: SQL_HANDLE_STMT Failed\n"); /* On se deconnecte avant de quitter */ status = SQLDisconnect(connection_handle); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLDisconnect failed\n"); SQLGetDiagRec(SQL_HANDLE_DBC, connection_handle, REC_NUMBER, state, &odbc_error, error_text, BUFFER_LEN, &error_text_len); fprintf(stderr, "%s (error number : %d)\n", error_text, (int)odbc_error); } SQLFreeHandle(SQL_HANDLE_DBC, connection_handle); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return KO; } /* Etape 4. executer la requete */ status = SQLExecDirect(statment_handle, (SQLCHAR *)STRING_REQUEST, SQL_NTS); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLExecDirect Failed\n"); /* On se deconnecte avant de quitter */ status = SQLDisconnect(connection_handle); if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { fprintf(stderr, "SQLDisconnect failed\n"); SQLGetDiagRec(SQL_HANDLE_DBC, connection_handle, REC_NUMBER, state, &odbc_error, error_text, BUFFER_LEN, &error_text_len); fprintf(stderr, "%s (error number : %d)\n", error_text, (int)odbc_error); } SQLFreeHandle(SQL_HANDLE_STMT, statment_handle); SQLFreeHandle(SQL_HANDLE_DBC, connection_handle); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return KO; } /* Etape 5. recuperer les resultats de la requete */ /* Correspondance entre les variables et leur position dans la requete : * emp_department_id correspond a department_id, il est en position 1 et est de type int * emp_first_name correspond a first_name, de type string et est en position 2 * emp_last_name correspond a last_name, de type string et est en position 3 * emp_salary correspond a salary, de type float et est en position 4 */ SQLBindCol(statment_handle, 1, SQL_C_LONG, &emp_department_id, sizeof(emp_department_id), &odbc_error); SQLBindCol(statment_handle, 2, SQL_C_CHAR, &emp_first_name, sizeof(emp_first_name), &odbc_error); SQLBindCol(statment_handle, 3, SQL_C_CHAR, &emp_last_name, sizeof(emp_last_name), &odbc_error); SQLBindCol(statment_handle, 4, SQL_C_FLOAT, &emp_salary, sizeof(emp_salary), &odbc_error); while(SQL_NO_DATA != (status = SQLFetch(statment_handle))) { fprintf(stdout, "<%d> - <%s> - <%s> - <%f>\n", emp_department_id, emp_first_name, emp_last_name, emp_salary); number_fetched_lines++; } /* Afficher le nombre de lignes retourné */ fprintf(stdout, "%d rows fetched\n", number_fetched_lines); SQLFreeHandle(SQL_HANDLE_STMT, statment_handle); SQLFreeHandle(SQL_HANDLE_DBC, connection_handle); SQLFreeHandle(SQL_HANDLE_ENV, environment_handle); return OK; } int main(int argc, char **argv) { if (argc != 4) { fprintf(stdout, "Usage : %s dataSource user password\n", argv[0]); return KO; } odbc_connection(argv[1], argv[2], argv[3]); return OK; }
Qu’on compile et exécute de la manière suivante :
toc@tocNewServer:~/toc_src/DATABASES$ gcc -Wall odbc-demo.c -o odbc-demo -lodbc toc@tocNewServer:~/toc_src/DATABASES$ ./odbc-demo Oracle hr human Connection to DataSource Oracle succeeded Database name : Oracle Database version : 11.02.0010 <50> - <Donald> - <OConnell> - <2600.000000> <50> - <Douglas> - <Grant> - <2600.000000> <10> - <Jennifer> - <Whalen> - <4400.000000> <20> - <Michael> - <Hartstein> - <13000.000000> <20> - <Pat> - <Fay> - <6000.000000> <40> - <Susan> - <Mavris> - <6500.000000> <70> - <Hermann> - <Baer> - <10000.000000> <110> - <Shelley> - <Higgins> - <12008.000000> <110> - <William> - <Gietz> - <8300.000000> <90> - <Steven> - <King> - <24000.000000> <90> - <Neena> - <Kochhar> - <17000.000000> <90> - <Lex> - <De Haan> - <17000.000000> <60> - <Alexander> - <Hunold> - <9000.000000> <60> - <Bruce> - <Ernst> - <6000.000000> <60> - <David> - <Austin> - <4800.000000> <60> - <Valli> - <Pataballa> - <4800.000000> <60> - <Diana> - <Lorentz> - <4200.000000> <100> - <Nancy> - <Greenberg> - <12008.000000> <100> - <Daniel> - <Faviet> - <9000.000000> <100> - <John> - <Chen> - <8200.000000> <100> - <Ismael> - <Sciarra> - <7700.000000> <100> - <Jose Manuel> - <Urman> - <7800.000000> <100> - <Luis> - <Popp> - <6900.000000> <30> - <Den> - <Raphaely> - <11000.000000> <30> - <Alexander> - <Khoo> - <3100.000000> <30> - <Shelli> - <Baida> - <2900.000000> <30> - <Sigal> - <Tobias> - <2800.000000> <30> - <Guy> - <Himuro> - <2600.000000> <30> - <Karen> - <Colmenares> - <2500.000000> <50> - <Matthew> - <Weiss> - <8000.000000> <50> - <Adam> - <Fripp> - <8200.000000> <50> - <Payam> - <Kaufling> - <7900.000000> <50> - <Shanta> - <Vollman> - <6500.000000> <50> - <Kevin> - <Mourgos> - <5800.000000> <50> - <Julia> - <Nayer> - <3200.000000> <50> - <Irene> - <Mikkilineni> - <2700.000000> <50> - <James> - <Landry> - <2400.000000> <50> - <Steven> - <Markle> - <2200.000000> <50> - <Laura> - <Bissot> - <3300.000000> <50> - <Mozhe> - <Atkinson> - <2800.000000> <50> - <James> - <Marlow> - <2500.000000> <50> - <TJ> - <Olson> - <2100.000000> <50> - <Jason> - <Mallin> - <3300.000000> <50> - <Michael> - <Rogers> - <2900.000000> <50> - <Ki> - <Gee> - <2400.000000> <50> - <Hazel> - <Philtanker> - <2200.000000> <50> - <Renske> - <Ladwig> - <3600.000000> <50> - <Stephen> - <Stiles> - <3200.000000> <50> - <John> - <Seo> - <2700.000000> <50> - <Joshua> - <Patel> - <2500.000000> <50> - <Trenna> - <Rajs> - <3500.000000> <50> - <Curtis> - <Davies> - <3100.000000> <50> - <Randall> - <Matos> - <2600.000000> <50> - <Peter> - <Vargas> - <2500.000000> <80> - <John> - <Russell> - <14000.000000> <80> - <Karen> - <Partners> - <13500.000000> <80> - <Alberto> - <Errazuriz> - <12000.000000> <80> - <Gerald> - <Cambrault> - <11000.000000> <80> - <Eleni> - <Zlotkey> - <10500.000000> <80> - <Peter> - <Tucker> - <10000.000000> <80> - <David> - <Bernstein> - <9500.000000> <80> - <Peter> - <Hall> - <9000.000000> <80> - <Christopher> - <Olsen> - <8000.000000> <80> - <Nanette> - <Cambrault> - <7500.000000> <80> - <Oliver> - <Tuvault> - <7000.000000> <80> - <Janette> - <King> - <10000.000000> <80> - <Patrick> - <Sully> - <9500.000000> <80> - <Allan> - <McEwen> - <9000.000000> <80> - <Lindsey> - <Smith> - <8000.000000> <80> - <Louise> - <Doran> - <7500.000000> <80> - <Sarath> - <Sewall> - <7000.000000> <80> - <Clara> - <Vishney> - <10500.000000> <80> - <Danielle> - <Greene> - <9500.000000> <80> - <Mattea> - <Marvins> - <7200.000000> <80> - <David> - <Lee> - <6800.000000> <80> - <Sundar> - <Ande> - <6400.000000> <80> - <Amit> - <Banda> - <6200.000000> <80> - <Lisa> - <Ozer> - <11500.000000> <80> - <Harrison> - <Bloom> - <10000.000000> <80> - <Tayler> - <Fox> - <9600.000000> <80> - <William> - <Smith> - <7400.000000> <80> - <Elizabeth> - <Bates> - <7300.000000> <80> - <Sundita> - <Kumar> - <6100.000000> <80> - <Ellen> - <Abel> - <11000.000000> <80> - <Alyssa> - <Hutton> - <8800.000000> <80> - <Jonathon> - <Taylor> - <8600.000000> <80> - <Jack> - <Livingston> - <8400.000000> <80> - <Charles> - <Johnson> - <6200.000000> <50> - <Winston> - <Taylor> - <3200.000000> <50> - <Jean> - <Fleaur> - <3100.000000> <50> - <Martha> - <Sullivan> - <2500.000000> <50> - <Girard> - <Geoni> - <2800.000000> <50> - <Nandita> - <Sarchand> - <4200.000000> <50> - <Alexis> - <Bull> - <4100.000000> <50> - <Julia> - <Dellinger> - <3400.000000> <50> - <Anthony> - <Cabrio> - <3000.000000> <50> - <Kelly> - <Chung> - <3800.000000> <50> - <Jennifer> - <Dilly> - <3600.000000> <50> - <Timothy> - <Gates> - <2900.000000> <50> - <Randall> - <Perkins> - <2500.000000> <50> - <Sarah> - <Bell> - <4000.000000> <50> - <Britney> - <Everett> - <3900.000000> <50> - <Samuel> - <McCain> - <3200.000000> <50> - <Vance> - <Jones> - <2800.000000> <50> - <Alana> - <Walsh> - <3100.000000> <50> - <Kevin> - <Feeney> - <3000.000000> 106 rows fetched toc@tocNewServer:~/toc_src/DATABASES$