Neviem ako vy, ale ja som z toho, že sa Oracle vydal cestou Express edícií, nadšený a taktiež som nadšený z toho, ako je navrhnuté OCCI. Páči sa mi jeho jednoduchosť a ľahká pochopiteľnosť. Dúfam, že ste toho istého názoru a chápanie OCCI vám ide ako po masle. V tomto diely si ukážeme, ako dáta z databázy vyťahovať, ako spracovávať SELECT-y a povieme si aj niečo o metadátach.
Podstatou a hlavnou myšlienkou každej databázy je základná práca s dátami. Ich ukladanie, modifikovanie, mazanie a v neposlednom rade aj ich získavanie. Vo svete SQL sa dá povedať, že ide o INSERT, UPDATE, DELETE a SELECT. Ostatne prvky ako triggre, procedúry, funkcie už len ďalej rozširujú možnosti DB, no podstata stále ostáva. V predchádzajúcich dieloch sme si ukázali, ako vykonávať prvé 3 operácie pomocou OCCI. Chýba nám už len dáta získavať pomocou SELECT-ov. To sa realizuje podobne ako predchádzajúce operácie, rozdiel je iba v spracovaní výsledku.
Výsledok SELECT-u a trieda ResultSet
SELECT je určitá forma dotazu na DB, u ktorej je dôležitý práve jej výsledok. Výsledok nieje jednoduchá veličina, číslo alebo text. Ide o množinu dát reprezentujúcu stĺpce a riadky. To, ako vykonávať dotaz, ako bindovať hodnoty, už vieme. Čo však nevieme je to, ako pristupovať k tejto množine dát. Pre SELECT-y sa používa špeciálne na tuto činnosť určená funkcia executeQuery(). Funkcia vráti inštanciu triedy ResulSet. Práve trieda ResultSet nám umožní pristupovať k riadkom a stĺpcom.
Trieda ResultSet spracováva riadok po riadku tzv. fetchovaním. Fetchovanie nasledujúceho riadku sa vykonáva volaním funkcie next(). V prípade, že už nieje čo fetchovať a dostali sme sa na samotný koniec, funkcia next() vráti hodnotu ResultSet::END_OF_FETCH. Je to podobný princíp ako sa používa pri načítaní súborov dovtedy, kým sa nenarazí na EOF. Riadok ma určité stĺpce. ResultSet k stĺpcom pristupuje pomocou indexov. To znamená pomocou poradia v SELECT-e. Jednotlivé hodnoty tak vieme získať opäť pomocou členských funkcii ResultSet-u ako getInt(), getString() atd.
Spracovanie SELECT-u je rovnako jednoduchá vec ako vykonanie UPDATE-u, o čom sa môžete presvedčiť na nasledujúcom príklade. Príklad vypíše výsledok jednoduchého SELECT-u.
main.cpp:#include <iostream>
#include <occi.h>
using namespace std;
using namespace oracle::occi;
#define USER "OCCIUSER"
#define PASS "****"
#define TNS "XE"
int main()
{
Environment* env;
Connection* con;
Statement* stm;
ResultSet* rs;
string meno;
string priezvisko;
int vek;
try {
//pripojenie na DB
env = Environment::createEnvironment();
con = env->createConnection(USER, PASS, TNS);
//priprava a vykonanie Query
stm = con->createStatement();
stm->setSQL("SELECT MENO, PRIEZVISKO, VEK FROM OSOBY");
rs = stm->executeQuery();
//spracovanie dat
while (rs->next() != ResultSet::END_OF_FETCH) {
meno = rs->getString(1);
priezvisko = rs->getString(2);
vek = rs->getInt(3);
cout << "fetched row" << endl;
cout << " column MENO: " << meno << endl;
cout << " column PRIEZVISKO: " << priezvisko << endl;
cout << " column VEK:" << vek << endl;
}
//updratanie
stm->closeResultSet(rs);
env->terminateConnection(con);
Environment::terminateEnvironment(env);
} catch (SQLException& e) {
cout << "ORACLE ERROR:" << e.getErrorCode() << " " << " - " << e.getMessage() << endl;
}
return 0;
}
Program jednoduchou formou vypíše cely obsah tabuľky OSOBY. V tomto programe je dôležitý cyklus spracovávania dát.
while (rs->next() != ResultSet::END_OF_FETCH) {
...
}
Tento cyklus je vykonávaný dovtedy, kým sa nespracujú všetky dáta, čiže funkcia next() nevráti hodnotu ResultSet::END_OF_FETCH. Vo vnútri cyklu pracujeme už s konkrétnym riadkom.
...
meno = rs->getString(1);
priezvisko = rs->getString(2);
vek = rs->getInt(3);
...
Hodnoty meno a priezvisko získavame pomocou funkcie getString(), nakoľko ide o text. Hodnotu vek zase získavame volaním funkcie getInt(). Keďže sa k stĺpcom pristupuje pomocou indexov, je potrebne si uvedomiť v akom poradí sa v SELECT-e nachádzajú. V našom prípade je napríklad prvý stĺpec MENO, preto použijeme getString(1). Po spracovaní všetkých riadkov je potrebne vykonať určitú očistu od ResultSet-u, uvolniť pamäť. To vykonáme pomocou closeResultSet() funkcie statementu.
Metadáta – kľuč k potrebným informáciám
V príklade sme k dátam v jednotlivých stĺpcoch pristupovali pomocou indexov. Tento spôsob je fajn, keď vieme čosi o SELECT-e. Sú situácie, kedy však potrebujeme väčšiu dynamiku a nevieme nič o tom, koľko stĺpov ma riadok, akého typu sú a aké sú veľké. Týmto informáciám sa hovory metadáta. Na tento účel sa v OCCI nachádza rovnomenná trieda MetaData a trieda ResultSet disponuje funkciou getColumnListMetaData(), ktorá vracia vektor metadát. Každý prvok v tomto vektore reprezentuje stĺpec.
Trieda MetaData je postavená na dvoch základných informáciách. Na type metadát a atribútov metadát. Typ metadát určuje, aké informácie MetaData obsahuje, napríklad informácie o tabuľke (PTYPE_TABLE), stĺpci (PTYPE_COL) alebo procedúre (PTYPE_PROC). Každý typ metadát má svoje atribúty. Atribúty slúžia na prístup ku konkrétnym informáciám, ktoré potrebujeme vedieť, napríklad meno stĺpca (ATTR_NAME) alebo typ stĺpca (ATTR_DATA_TYPE). Teraz si upravíme náš kód tak, aby vypisoval obsah stĺpcov dynamickejšie, kde typ dát meno stĺpca bude získavané pomocou metadát.
main.cpp:#include <iostream>
#include <vector>
#include <occi.h>
using namespace std;
using namespace oracle::occi;
#define USER "OCCIUSER"
#define PASS "****"
#define TNS "XE"
void processResult(ResultSet* rs)
{
vector<MetaData> meta_data = rs->getColumnListMetaData();
//spracovanie riadkov
while (rs->next() != ResultSet::END_OF_FETCH) {
cout << "fetched row" << endl;
//spracovanie stlpcov
for (unsigned int i = 0; i < meta_data.size(); i++) {
cout << " column " << meta_data[i].getString(MetaData::ATTR_NAME);
switch (meta_data[i].getInt(MetaData::ATTR_DATA_TYPE)) {
case OCCI_SQLT_CHR:
cout << ":" << rs->getString(i+1);
break;
case OCCI_SQLT_NUM:
cout << ":" << rs->getInt(i+1);
break;
}
cout << endl;
}
}
}
int main()
{
Environment* env;
Connection* con;
Statement* stm;
ResultSet* rs;
try {
env = Environment::createEnvironment();
con = env->createConnection(USER, PASS, TNS);
stm = con->createStatement();
stm->setSQL("SELECT MENO, PRIEZVISKO, VEK FROM OSOBY");
rs = stm->executeQuery();
processResult(rs);
stm->closeResultSet(rs);
env->terminateConnection(con);
Environment::terminateEnvironment(env);
} catch (SQLException& e) {
cout << "ORACLE ERROR:" << e.getErrorCode() << " " << " - " << e.getMessage() << endl;
}
return 0;
}
Výsledok je prakticky ten istý, program opäť vypíše obsah tabuľky OSOBY. Dôležité u tohto programu je to, že už niesme viazaný na presné poradie stĺpcov v SELECT-e, čiže akonáhle by sme zmenili poradie, pridali novy stĺpec do SELECT-u, program na základe metadát vykoná korektný výpis.
Zmena nastala v spracovaní jednotlivých riadkov. Cele spracovanie riadkov som umiestnil do funkcie processResult(). Dôvodom je použitie práve metadáta. Ak si všimnete, metadáta sú alokované na zásobníku. Ak by som pracú z MetaData umiestnil do main() funkcie, kde je volané terminateEnvironment(), došlo by k segmentation fault chybe, pretože metadáta by sa uvoľňovali až po terminateEnfironment(). Ďalej bol pridaný cyklus, ktorý prechádza všetky metadáta vo vektore, ktoré obsahujú informácie o jednotlivých stĺpcov. Ďalej v kóde zisťujeme pomocou getString() hodnotu atribútu ATTR_NAME, čo je v konečnom dôsledku meno stĺpca.
for (unsigned int i = 0; i < meta_data.size(); i++) {
cout << " column " << meta_data[i].getString(MetaData::ATTR_NAME);
Okrem mena stĺpca zisťujeme ešte typ stĺpca. Je to dôležité preto, aby sme vedeli, akým spôsobom k dátam pristupovať, čí sa jedna o číslo, text, alebo dátum. K tomuto zase slúži atribút ATTR_DATA_TYPE.
switch (meta_data[i].getInt(MetaData::ATTR_DATA_TYPE)) {
case OCCI_SQLT_CHR:
cout << ":" << rs->getString(i+1);
break;
case OCCI_SQLT_NUM:
cout << ":" << rs->getInt(i+1);
break;
}
Ten však nevracia textovú formu typu, ale int číslo. Preto sme použili funkciu getInt(). Toto číslo ďalej spracujeme pomocou switch-case podmienky. OCCI ma v header-och definície týchto čísel o aké typy ide. V tomto prípade ak pôjde o oracle typ VARCHAR2 , čiže OCCI_SQLT_CHR, dáta získame zo statementu pomocou getString(). Ak pôjde o oracle typ NUMBER, čiže OCCI_SQLT_NUM, dáta získame pomocou getInt().
Pomocou metadát môžeme zisťovať rôzne informácie o tabuľkách, samotnej databáze, schéme, procedúre. Spôsob je jednoduchý. Základ je získať metadáta. Objekt Connection ma pravé na tento účel funkciu getMetaData(). V nasej experimentálnej databáze mame s predchádzajúceho článku vytvorenú procedúru MYPROC. Metadáta tejto procedúry získame nasledujúcim riadkom:
MetaData mt_proc = con->getMetaData("MYPROC", MetaData::PTYPE_PROC);
Ak budete pátrať v dokumentácii ORACLE, zistíte, že typ PTYPE_PROC obsahuje jeden zaujímavý atribút ATTR_LIST_ARGUMENTS. Ide o atribút, ktorý vráti opäť pole metadát reprezentujúcich jednotlivé argumenty. Keďže ide o pole, vektor MetaData, pristupujeme k nemu pomocou funkcie getVector().
vector<MetaData> args = mt_proc.getVector(MetaData::ATTR_LIST_ARGUMENTS);
Argumenty procedúr a funkcii majú množstvo atribútov. Nasledujúci krátky príklad pracuje len s niektorými a jeho úlohou je vypísať mena argumentov a ich mód pomocou atribútu ATTR_IOMODE, čí sú vstupné, výstupné alebo vstupne-výstupné.
main.cpp:#include <iostream>
#include <vector>
#include <occi.h>
using namespace std;
using namespace oracle::occi;
#define USER "OCCIUSER"
#define PASS "****"
#define TNS "XE"
void processMyprocAttrs(Connection* con)
{
//spracovanie metadat procedury
MetaData mt_proc = con->getMetaData("MYPROC", MetaData::PTYPE_PROC);
vector<MetaData> args = mt_proc.getVector(MetaData::ATTR_LIST_ARGUMENTS);
//spracovanie metadat argumentov procedury
for (unsigned int i = 0; i < args.size(); i++) {
cout << "Parameter " << args[i].getString(MetaData::ATTR_NAME);
switch (args[i].getInt(MetaData::ATTR_IOMODE)) {
case 0:
cout << " IN";
break;
case 1:
cout << " OUT";
break;
case 2:
cout << " IN/OUT";
break;
}
cout << endl;
}
}
int main()
{
Environment* env;
Connection* con;
try {
//vytvorenie spojenia
env = Environment::createEnvironment();
con = env->createConnection(USER, PASS, TNS);
processMyprocAttrs(con);
//upratanie
env->terminateConnection(con);
Environment::terminateEnvironment(env);
} catch (SQLException& e) {
cout << "ORACLE ERROR:" << e.getErrorCode() << " " << " - " << e.getMessage() << endl;
}
return 0;
}
Popisovať tu každý typ metadát a každý atribút je mimo rámec článku, nakoľko sa takýmto spôsobom dá pristupovať k množstvu informácii. Teraz by ste už však mali vedieť o tom, že nejaké metadáta existujú a ako ich približne používať. Popis atribútov a typov už nájdete v ORACLE dokumentácii na www.oracle.com.