2. Getting started with OTL
This document provides information on the Oracle
Call Interface
Template Library (OTL). OTL is a new kind of C++ libraries, similar to
the Standard Template Library. This kind of libraries is easy to use,
since the user needs only to include C++ header files which contain
template classes and functions. There is no need to link additional
object libraries into C++ applications. The code, instantiated from
template classes and inline functions, is efficient and reliable in
terms of runtime performance and C++ strict type checking.
OTL comprises of a set of template classes. The
templates allow the
user to create scalar host variables and host arrays, then dynamically
bind the variables and arrays with SQL statements or PL/SQL
blocks. OTL has a number of non-template classes which encapsulate the
Oracle Call Interface (OCI) functions and provide transparent
programming interface to them.
OTL provides an optional exception handling
mechanism, given in the
form of the otl_exception class. This mechanism takes advantage of C++
exceptions compared to coding database applications in plain C. The
user does not need to check out return codes after each function
call. The code, instantiated from the OTL templates and inline
functions, is much nicer and cleaner in comparison with the code
generated by the Pro*C precompiler.
In OTL, a concept of SQL
streams is
introduced. The SQL programming interface becomes unified and
homogeneous.
OTL has a simplified set of functions, called Prosto*C.
It provides basic functions, such as
connect/disconnect, printf/scanf, commit/rollback, etc. The word
"Prosto*C" is originated in the author's native language: "prosto"
means "simple". The idea here is to simplify the interface as much as
possible, without losing the functionality.
OTL is a database access function library. Also, this document
describes the Pro*OTL / Pre-Pro*C
preprocessor (PPC). PPC is a preprocessor which takes a directive
file on input and generates OTL and Pro*C code on output. Besides, PPC
produces a header file with prototypes of the generated functions and
data structures, used by the functions. The generated functions are
the "executable" form of the directives. The directives are more
declarative and much more compact than the corresponding
functions. The generated header file can be included in both C++ and
plain C modules.
OTL, as a library, and PPC, as a preprocessor, are intended to
boost
productivity of Oracle database developers who work with C++ as well
as Pro*C. PPC is a pathway from traditional Pro*C to more advanced
C++ database APIs.
OTL and PPC compile with the following 32-bit C++ compilers:
- IBM AIX, C++ (xlC), 1.x and higher
- SunOS/Solaris, Sun C++, 4.x
- Unix, GNU C++ (g++), 2.7.x
- Windows 95, NT, Visual C++, 4.x, 32-bit
The author is hoping to get feedback from potential users of OTL and
that the OTL source code is clean enough to be ported across the
32-bit platforms, different from the mentioned above.
Besides, the author's goal is to eventually find a sponsor to make
this product commercial, in order to enhance, maintaion and support it
on the regular basis.
Despite the common opinion that Freeware products are not that good
and badly supported, the author believes that OTL & PPC have
production quality and can be used successfully.
The OTL source code resides in Appendix C, PPC --
in Appendix D. The whole page may be downloaded,
in order to get the source code. Examples may be clipped from the
text, copied to separate files and used. Comments and questions would
be appreciated very much. Email to skuchin@gmail.comskuchin@gmail.com.
Let's assume you want to create a table, fill it
out with a hundred
records and then select some of them. This may be accomplished by the
following code.
#include <iostream>
using namespace std;
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int STR_SIZE=31; // string size
void insert() // insert rows into table
{ float f1[BUF_SIZE]; // float host array f1 without indicators char
f2[BUF_SIZE][STR_SIZE]; // string host array f2 without indicators
otl_cursor o(db); // create cursor int n=0; o.parse("insert into
test_tab values(:f1,:f2)"); // parse sql statement
o.bind_float(":f1",f1); // bind f1
o.bind_cstring(":f2",(char*)f2,STR_SIZE); // bind f2 for(int
i=1;i<=100;++i){ ++n; f1[n-1]=i; // fill out host array f1
sprintf(f2[n-1],"Name%d",i); // fill out host array f2 if(n==BUF_SIZE){
// execute the sql statement when buffer gets // full o.exec(n); n=0; }
} if(n>0) o.exec(n); db.commit(); // commit transaction
} /* insert */
void select()
{ float f1[BUF_SIZE]; // float host array f1 without indicators char
f2[BUF_SIZE][STR_SIZE]; // string host array f2 without indicators int
f; // host variable f without indicator otl_select_cursor
i(db,BUF_SIZE); // create specialized select cursor i.parse("select *
from test_tab where f1>=:f and f1<=:f*2"); // parse select
statement i.bind_float(1,f1); // bind f1 to column 1
i.bind_cstring(2,(char*)f2,STR_SIZE); // bind f2 to column 2
i.bind_int(":f",&f); // bind f f=8; // assign 8 to f
while(i.next()){ // while not end-of-data int k=i.cur_row; // index of
current row in host arrays cout<<"f1="<<f1[k]<<",
f2="<<f2[k]<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ cerr<<p.msg<<endl; // print out error message }
db.logoff(); //disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int STR_SIZE=31; // string size
void insert()
// insert rows into table
{ otl_float_array<BUF_SIZE> f1; // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2; // C-string host array
f2 otl_cursor o(db); // create cursor int n=0; o.parse("insert into
test_tab values(:f1,:f2)"); // parse sql statement o.bind(":f1",f1); //
bind f1 o.bind(":f2",f2); // bind f2 for(int i=1;i<=100;++i){ ++n;
f1.v[n-1]=i; // fill out host array f1 sprintf(f2.v[n-1],"Name%d",i);
// fill out host array f2 if(n==BUF_SIZE){ // execute sql statement
when buffer gets // full o.exec(n); n=0; } } if(n>0) o.exec(n);
db.commit(); // commit transaction
} /* insert */
void select()
{ otl_float_array<BUF_SIZE> f1; // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2; // string host array f2
otl_int f; // host variable f otl_select_cursor i(db,BUF_SIZE); //
create specialized select cursor i.parse("select * from test_tab where
f1>=:f and f1<=:f*2"); // parse select statement i.bind(1,f1); //
bind f1 to column 1 i.bind(2,f2); // bind f2 to column 2
i.bind(":f",f); // bind f. f is input variable f.v=8; // assign 8 to f
while(i.next()){ // while not end-of-data int k=i.cur_row; // index of
current row in host arrays cout<<"f1="<<f1.v[k]<<",
f2="<<f2.v[k]<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int STR_SIZE=31; // string size
void insert()
// insert rows into table
{ otl_float_array<BUF_SIZE> f1(":f1"); // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2(":f2"); // C-string host
array f2 otl_cursor o(db); // create cursor int n=0; o.eparse("insert
into test_tab values(:f1,:f2)",&f1,&f2,0); // parse sql
statement and bind variables f1 and f2 with the // statement. the
variable list is NULL terminated for(int i=1;i<=100;++i){ ++n;
f1.v[n-1]=i; // fill out host array f1 sprintf(f2.v[n-1],"Name%d",i);
// fill out host array f2 if(n==BUF_SIZE){ // execute sql statement
when buffer gets // full o.exec(n); n=0; } } if(n>0) o.exec(n);
db.commit(); // commit transaction
} /* insert */
void select()
{ otl_float_array<BUF_SIZE> f1; // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2; // string host array f2
otl_int f(":f"); // host variable f otl_select_cursor i(db,BUF_SIZE);
// create specialized select cursor i.eparse("select * from test_tab
where f1>=:f and f1<=:f*2",&f1,&f2,&f,0); // parse
select statement, bind input variable f and output columns // f1, f2
with the statement. f1 is treated as column 1 in the // select list, f2
-- as column 2. The variable list is NULL terminated f.v=8; // assign 8
to f while(i.next()){ // while not end-of-data int k=i.cur_row; //
index of current row in host arrays
cout<<"f1="<<f1.v[k]<<",
f2="<<f2.v[k]<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int LONG_STR_SIZE=2000; // LONG string size
void insert()
// insert rows into table
{ otl_float f1(":f1"); // float host variable f1
otl_long_varchar<LONG_STR_SIZE> f2(":f2"); // long varchar host
variable f2 otl_cursor o(db); // create cursor o.eparse("insert into
test_tab values(:f1,:f2)",&f1,&f2,0); // parse sql statement
and bind variables f1 and f2 with the // statement. the variable list
is NULL terminated for(int i=1;i<=9;++i){ f1.v=i; // assign host
variable f1 f2.set_len(5); // set the long_varchar string length
f2[0]='N'; f2[1]='a'; f2[2]='m'; f2[3]='e'; f2[4]='0'+i; o.exec();
db.commit(); // commit transaction }
} /* insert */
void select()
{ otl_float f1; // float f1 otl_long_varchar<LONG_STR_SIZE> f2;
otl_int f(":f"); // host variable f otl_select_cursor i(db); // create
specialized select cursor i.eparse("select * from test_tab where
f1>=:f and f1<=:f*2",&f1,&f2,&f,0); // parse select
statement, bind input variable f and output columns // f1, f2 with the
statement. f1 is treated as column 1 in the // select list, f2 -- as
column 2. The variable list is NULL terminated f.v=4; // assign 4 to f
while(i.next()){ // while not end-of-data
cout<<"f1="<<f1.v<<", f2="; for(int
j=0;j<f2.len();++j) cout<<f2[j]; // print out the long_varchar
string cout<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
long)" ); // create table insert(); // insert records into table
select(); // select records from table } catch(otl_exception& p){
// intercept OTL exceptions cerr<<p.msg<<endl; // print out
error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int STR_SIZE=31; // string size
void insert()
// insert rows into table
{ otl_float_array<BUF_SIZE> f1(":f1"); // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2(":f2"); // C-string host
array f2 otl_cursor o(db); // create cursor int n=0; o.eparse("insert
into test_tab values(:f1,:f2)",&f1,&f2,0); // parse sql
statement and bind variables f1 and f2 with the // statement. the
variable list is NULL terminated for(int i=1;i<=100;++i){ ++n;
f1.v[n-1]=i; // fill out host array f1 sprintf(f2.v[n-1],"Name%d",i);
// fill out host array f2 if(n==BUF_SIZE){ // execute sql statement
when buffer gets // full o.exec(n); n=0; } } if(n>0) o.exec(n);
db.commit(); // commit transaction
} /* insert */
void select()
{ otl_int f(":f"); // host variable f otl_select_stream i(db, "select *
from test_tab where f1>=:f and f1<=:f*2", BUF_SIZE, // size of
the buffer attached to the // stream &f, // input variable :f2 0 //
NULL terminator of the variable list ); // create select stream int f1;
char f2[STR_SIZE]; f.v=8; // assign 8 to f i.rewind(); // rewind the
stream: re-execute the statement and fetch // first portion of rows.
while(!i.eof()){ // while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
f.v=4; // assign 4 to f i.rewind(); // rewind the stream: re-execute
the statement and fetch // first portion of rows. while(!i.eof()){ //
while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
const int BUF_SIZE=50; // host array size
const int STR_SIZE=31; // string size
void insert()
// insert rows into table
{ otl_float_array<BUF_SIZE> f1(":f1"); // float host array f1
otl_cstring_array<BUF_SIZE,STR_SIZE> f2(":f2"); // C-string host
array f2 otl_out_stream o(db, // connect object "insert into test_tab
values(:f1,:f2)", // SQL statement &f1, // bind variable :f1
&f2, // bind variable :f2 0 // end of variable list ); char
tmp[31]; for(int i=1;i<=100;++i){ sprintf(tmp,"Name%d",i);
o<<(float)i<<tmp; }
} /* insert */
void select()
{ otl_int f(":f"); // host variable f otl_select_stream i(db, "select *
from test_tab where f1>=:f and f1<=:f*2", BUF_SIZE, // size of
the buffer attached to the // stream &f, // input variable :f2 0 //
NULL terminator of the variable list ); // create select stream int f1;
char f2[STR_SIZE]; f.v=8; // assign 8 to f i.rewind(); // rewind the
stream: re-execute the statement and fetch // first portion of rows.
while(!i.eof()){ // while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
f.v=4; // assign 4 to f i.rewind(); // rewind the stream: re-execute
the statement and fetch // first portion of rows. while(!i.eof()){ //
while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
void insert()
// insert rows into table
{ otl_inout_stream o(50, // buffer size "insert into test_tab
values(:f1<float>,:f2<char[31]>)", // SQL statement db //
connect object ); char tmp[32]; for(int i=1;i<=100;++i){
sprintf(tmp,"Name%d",i); o<<(float)i<<tmp; }
} /* insert */
void select()
{ otl_select_stream i(50, // buffer size "select * from test_tab where
f1>=:f<int> and f1<=:f*2", // SELECT statement db //
connect object ); // create select stream int f1; char f2[31];
i<<8; // assigning :f = 8 // SELECT automatically executes when
all input variables are // assigned. First portion of out rows is
fetched to the buffer while(!i.eof()){ // while not end-of-data
i>>f1>>f2; cout<<"f1="<<f1<<",
f2="<<f2<<endl; } i<<4; // assigning :f = 4 // SELECT
automatically re-executes when all input variables are // assigned.
First portion of out rows is fetched to the buffer while(!i.eof()){ //
while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
void insert()
// insert rows into table
{ otl_stream o(50, // buffer size "insert into test_tab
values(:f1<float>,:f2<char[31]>)", // SQL statement db //
connect object ); char tmp[32]; for(int i=1;i<=100;++i){
sprintf(tmp,"Name%d",i); o<<(float)i<<tmp; }
} /* insert */
void select()
{ otl_stream i(50, // buffer size "select * from test_tab where
f1>=:f<int> and f1<=:f*2", // SELECT statement db //
connect object ); // create select stream int f1; char f2[31];
i<<8; // assigning :f = 8 // SELECT automatically executes when
all input variables are // assigned. First portion of out rows is
fetched to the buffer while(!i.eof()){ // while not end-of-data
i>>f1>>f2; cout<<"f1="<<f1<<",
f2="<<f2<<endl; } i<<4; // assigning :f = 4 // SELECT
automatically re-executes when all input variables are // assigned.
First portion of out rows is fetched to the buffer while(!i.eof()){ //
while not end-of-data i>>f1>>f2;
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
void plsql()
// invoking PL/SQL block
{ otl_stream o(5, // buffer size "begin " " :A<int,inout> :=
:A+1; " " :B<char[31],out> := :C<char[31],in>; " "end;", //
PL/SQL block db // connect object ); o<<1<<"Test String1";
// assigning :A = 1, :C = "Test String1" o<<2<<"Test
String2"; // assigning :A = 2, :C = "Test String2"
o<<3<<"Test String3"; // assigning :A = 3, :C = "Test
String3" o.flush(); // executing PL/SQL block 3 times int a; char
b[32]; while(!o.eof()){ // not end-of-data o>>a>>b;
cout<<"A="<<a<<", B="<<b<<endl; }
} /* plsql */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle plsql(); //
invoking PL/SQL block } catch(otl_exception& p){ // intercept OTL
exceptions cerr<<p.msg<<endl; // print out error message }
db.logoff(); // disconnect from Oracle return 0;
} /* main */
A=2, B=Test String1
A=3, B=Test String2
A=4, B=Test String3
#include <iostream.h>
#include <stdio.h>
#include <otl.h>
otl_connect db; // connect object
void insert()
// insert rows into table
{ otl_stream o(50, // buffer size "insert into test_tab
values(:f1<int>,:f2<char[31]>)", // SQL statement db //
connect object ); char tmp[32]; for(int i=1;i<=100;++i){
sprintf(tmp,"Name%d",i); o.printf("%d %s",i,tmp); // write one row into
stream }
} /* insert */
void select()
{ otl_stream i(50, // buffer size "select * from test_tab where
f1>=:f<int> and f1<=:f*2", // SELECT statement db //
connect object ); // create select stream int f1; char f2[31];
i<<8; // assigning :f = 8 // SELECT automatically executes when
all input variables are // assigned. First portion of out rows is
fetched to the buffer while(!i.eof()){ // while not end-of-data
i.scanf("%d %s",&f1,f2); // read one row from stream
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
i<<4; // assigning :f = 4 // SELECT automatically re-executes
when all input variables are // assigned. First portion of out rows is
fetched to the buffer while(!i.eof()){ // while not end-of-data
i.scanf("%d %s",&f1,f2); // read one row from stream
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
} /* select */
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table test_tab",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table test_tab(f1 number, f2
varchar2(30))" ); // create table insert(); // insert records into
table select(); // select records from table } catch(otl_exception&
p){ // intercept OTL exceptions cerr<<p.msg<<endl; // print
out error message } db.logoff(); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <otl.h>
otl_connect* db=0; // pointer to connect object
void my_handler(char* msg, int code)
// my error handler
{ cout<<msg<<endl; if(db)db->rollback(); // on error,
roll back transaction if already // connected exit(1); // exit from the
program
}
void insert()
// insert rows into table
{ otl_stream* o=otl_stream_open // open OTL stream ( db, // connect
object "insert into test_tab
values(:f1<int>,:f2<char[31]>)", // SQL statement 50 //
buffer size ); char tmp[32]; for(int i=1;i<=100;++i){
sprintf(tmp,"Name%d",i); otl_printf(o,"%d %s",i,tmp); // write one row
into the stream } otl_stream_close(o); // Close OTL stream
} /* insert */
void select()
{ otl_stream* i=otl_stream_open // Open OTL stream ( db, // connect
object "select * from test_tab where f1>=:f<int> and
f1<=:f*2", // SELECT statement 50 // buffer size ); // create select
stream int f1; char f2[31]; otl_printf(i,"%d",8); // assigning :f = 8
// SELECT automatically executes when all input variables are //
assigned. First portion of out rows is fetched to the buffer
while(!otl_eof(i)){ // while not end-of-data otl_scanf(i,"%d
%s",&f1,f2); // fetch a row from the stream
cout<<"f1="<<f1<<", f2="<<f2<<endl; }
otl_printf(i,"%d",4); // assigning :f = 4 // SELECT automatically
executes when all input variables are // assigned. First portion of out
rows is fetched to the buffer while(!otl_eof(i)){ // while not
end-of-data otl_scanf(i,"%d %s",&f1,f2); // fetch a row from the
stream cout<<"f1="<<f1<<",
f2="<<f2<<endl; } otl_stream_close(i); // Close OTL stream
} /* select */
int main()
{
// connect to Oracle db=otl_logon("scott/tiger", my_handler //
attaching error handler to the connect object ); otl_exec( db, "drop
table test_tab", 1 // ignore error ); // drop table otl_exec( db,
"create table test_tab(f1 number, f2 varchar2(30))" ); // create table
insert(); // insert records into table select(); // select records from
table otl_logoff(db); // disconnect from Oracle return 0;
} /* main */
f1=8, f2=Name8
f1=9, f2=Name9
f1=10, f2=Name10
f1=11, f2=Name11
f1=12, f2=Name12
f1=13, f2=Name13
f1=14, f2=Name14
f1=15, f2=Name15
f1=16, f2=Name16
f1=4, f2=Name4
f1=5, f2=Name5
f1=6, f2=Name6
f1=7, f2=Name7
f1=8, f2=Name8
In this section, OTL is compared with the other
C++/database
libraries, namely:
SQLObjects and DBTools.h++ are both commercial, multi-platform,
multi-database C++ database access libraries. It is hard to
perform good comparison between them and OTL, since they provide
similar functionality but belong to the different
category. Nevertheless, OTL has advantage of being well suited
for a range of Oracle applications and platforms. It is portable
and easy-to-maintain within this range.
Multi-database C++ libraries lose the sense of "closeness" with
the database. They, most often, do not have so called "native
access" to the database. All their multi-database functionality
is based upon the monster called ODBC. Oracle has many advantages
compared to the other databases, such as Sybase, Informix, etc.
It is not such a bad idea to harness most of the power Oracle
provides:
- PL/SQL functions/procedures/packages
- Other Oracle SQL extentions
- Oracle Array Interface
Typical problem of accessing Oracle via ODBC is that no ODBC
driver provides transparent access to PL/SQL, especially with
OUT or IN OUT parameters.
Often, it is critical to have fast interface. If the interface is
fast, then, most probably, it is not portable accross database
servers from different vendors. If the interface is portable and
unified, then it is awfully slow.
Considering the fact that a big segment of the database market is
covered by Oracle, it makes a lot of sense to develop an
interface unified for Oracle across most of the platforms on
which Oracle runs.
Let's consider a typical C++ interface to a database. It provides
a set of functions like:
- Connect/Disconnect
- Open/Close cursor
- Parse/Execute SQL statement
- Bind host variables
- Fetch rows from a SELECT statement
- Get and process database errors
Usually, such a layer of code is called access library. Some
vendors go far beyond that and provide factories, based upon
such access libraries. The factory is a GUI based front end which
generates the access library calls. Does it really improve the
developer's productivity? Sometimes, yes.
In contrast, OTL provides a multilayer class hierarchy and allows the
database programmer to decide which layer is more appropriate for each
case. The main advantage of OTL is that it provides the code layers
both as low as the Oracle Call Interface and as high as abstract and
unified SQL stream interface.
Therefore, OTL, in a sense, fills out the gap between the access layer
of code and the factory. The database programmer does not have to
learn tons of new front ends. He has to deals only with the same C++
code and the database.
It is not hard to imagine that it is rather easy to build up a
factory on top of OTL, as access library. The code, generated by
the factory would occupy less memory and would be more readable
than the code, generated into the calls of a low level access
library. The Pro*OTL / Pre-Pro*C preprocessor
may be the first step toward such a factory.
Step 1 char szName[31]="";
Step 2 char szAddress[31]="";
Step 3 SQLObject *pDb=new SQLSybase(); // Instantiate SQLObject object
Step 4 pDb->sqlLogin(); // Log into the database
Step 5 pDb->sqlExec("SELECT name, address FROM customer"); //
execute the SQL command
Step 6 pDb->sqlBind(1,bindZSTRING,31,szName);
Step 7 pDb->sqlBind(2,bindZSTRING,31,szAddress); // Binds the host
variables to the column results
Step 8 while(pDb->sqlFetch() == TRUE) { cout << szName
<< " " << szAddress << "\n"; } // Fetches the records
from the data source
Step 9 pDb->sqlDisconnect(); // Disconnects from the data source
Step 10 delete pDb; // Deletes the object
"As high as abstract and unified SQL stream
interface":
Step 1 char szName[31];
Step 2 char szAddress[31];
Step 3 otl_stream s(20, // stream buffer size (in rows) "SELECT name,
address FROM customer", // SELECT statement db // connect object ; Step
4,5,6,7 // no explicit binding, no explicit execution
Step 8 while(!s.eof()) { s >> szName >> szAddress; // fetch
one row cout << szName << " " << szAddress <<
endl; } // Fetches the records from the data source
Step 9, 10 // Automatic destructor will do the job
"As low as the Oracle Call Interface interface":
Step 1 char szName[31];
Step 2 char szAddress[31];
Step 3 otl_select_cursor s(db); // db -- connect object // Instantiate
Cursor object
Step 4 // Everything is done in the constructor
Step 5 s.parse("SELECT name, address FROM customer"); // Parse the SQL
command
Step 6 s.bind_cstring(1,szName,30);
Step 7 s.bind_cstring(2,sAddress,30); // Binds the host variables to
the column results
Step 8 while(s.next()) { cout << szName << " " <<
szAddress << endl; } // Fetches the records from the data source
Step 9, 10 // Automatic constructor will do the job
// This example establishes a connection to a SYBASE database, creates
// a table on the server, and uses the reader to read back the values.
// The values are then stored on a Memory Table and accessed row by
// row. This example can run on both WINDOWS and SUN/SOLARIS or
// SUNOS.
//#define UNIX // To run examples on Unix SunOS/Solaris
#define WINDOWS // To run example on WINDOWS
#include <rw/db/db.h>
#include <rw/db/dbmgr.h>
#ifdef WINDOWS
#include <windows.h>
#endif
void write(const RWCString& w)
{
#ifdef WINDOWS MessageBox(0,w.data(),"DBTOOLS_BOX",MB_OK);
#elif defined(UNIX) cout << w.data() << endl;
#endif
}
void errorHandler(const RWDBStatus& s)
{ char buf[1040]; sprintf(buf,"Message: %s ",s.message().data());
write(RWCString(buf));
}
#ifdef WINDOWS
#pragma argsused
int PASCAL WinMain(HINSTANCE , HINSTANCE, LPSTR, int )
{ RWCString serverType("bdbsdld.dll");
#elif defined(UNIX)
int main()
{ RWCString serverType("SYBASE");
#endif // Set the Error Handler
RWDBManager::setErrorHandler(errorHandler); // Establish Connection
RWDBDatabase adb = RWDBManager::database(serverType, "SYBASE100", // to
the database. "henri", "meli12", "SUPPORT"); if(!adb.isValid())
write("Hmmm !!! Cannot Connect. Exit..."); else { RWDBConnection
conn=adb.connection(); if(adb.table("TABLE1").exists())
adb.table("TABLE1").drop(); RWDBSchema mySchema;
mySchema.appendColumn("id_num",RWDBValue::Int);
mySchema.appendColumn("id_type", RWDBValue::String, 15);
mySchema.appendColumn("input_date", RWDBValue::DateTime);
mySchema.appendColumn("price", RWDBValue::Float);
adb.createTable("TABLE1",mySchema, conn); RWDBTable
tab=adb.table("TABLE1"); if(tab.exists()) write("Table 'TABLE1'
successfully created."); // Insert about 10 Values int
numberOfEntries=5; char buffer[1040]; RWDBDateTime date; float
price=1.5f; RWDBInserter ins = tab.inserter(); for(int i=0;
i<numberOfEntries; i++) { sprintf(buffer,"Item%d",i);
date.addDays(1); ins << i << RWCString(buffer) <<
date << << i*price; ins.execute(conn); } // Select all of
items RWDBSelector sel = adb.selector(); sel << tab; RWDBResult
res = sel.execute(conn); RWDBReader rdr=res.table().reader(conn);
RWDBMemTable memTab(rdr,numberOfEntries);
for(i=0;i<memTab.entries(); i++) { sprintf(buffer,"ROW[%d] ID_NUMBER
%d ID_TYPE %s \n INPUT_DATE %s INPUT_PRICE %f\n", i,
(memTab[i][0]).asInt(), (memTab[i][1]).asString().data(),
(memTab[i][2]).asString().data(), (memTab[i][3]).asFloat());
write(RWCString(buffer)); } return 0; } return 0;
}
// This example establishes a connection to an Oracle database, creates
// a table on the server, and reads the values back.
#include <stdio.h>
#include <iostream.h>
#include <otl.h>
otl_connect db; // connect object
void write(const char* w)
{ cout << w << endl; // Write a message
}
int main()
{ try{ db.rlogon("scott/tiger"); // connect to Oracle
otl_cursor::direct_exec ( db, "drop table TABLE1",
otl_exception::disabled // disable OTL exceptions ); // drop table
otl_cursor::direct_exec ( db, "create table TABLE1" "( " " id_num
number(8)," " id_type varchar2(15)," " input_date date," " price
number" ")" ); // create table write("Table 'TABLE1' successfully
created."); // Insert about 10 Values { int numberOfEntries=5; char
buffer[1040]; int date=0; float price=1.5f; otl_stream ins(50, //
buffer size (in rows) "insert into TABLE1 values(" "
:id_num<int>," " :id_type<char[64]>," "
sysdate+:input_date<int>," " :price<float>" ")", db //
connect object ); for(int i=0; i<numberOfEntries; i++){
sprintf(buffer,"Item%d",i); ++date; ins << i << buffer
<< date << (float) i*price; // insert one row into the
table } } // Select all of items { char buffer[1040]; int count=0; int
id_num; char id_type[64]; char input_date[32]; float price; otl_stream
sel(10, // buffer size (in rows) "select * TABLE1" db // connect object
); while(!sel.eof()){ sel >> id_num >> id_type >>
input_date >> price; // get one row sprintf(buffer, "ROW[%d]
ID_NUMBER %d ID_TYPE %s \n" "INPUT_DATE %s INPUT_PRICE %f\n", ++count,
id_num, id_type, input_date, price ); } } } catch(otl_exception&
p){ // intercept OTL exceptions write(p.msg); } db.logoff(); //
disconnect from Oracle return 0;
}
OTL falls into two parts: template classes to
create host variables
and arrays and non-template classes to provide programming interface
to the Oracle Call Interface.
Two types of host objects are distinguished: scalar
variables and arrays. OTL has two generic template classes
(otl_variable and otl_array) from which the following
specialized classes are derived:
The otl_variable and otl_array classes define the
following
kinds of buffers which are necessary for handling host
variables:
These data members are defined to be public, so the
user has
access to them and may freely assign and change their
values.
The otl_variable and otl_array classes have a
common parent
(otl_generic_variable) which contains information about the buffer
addresses, dimensions and data type codes. When a host variable or
array is constructed from an instantiated template, constructors of
the corresponding template classes initialize the data members of the
otl_generic_variable class. otl_cursor has a couple of the bind
functions which have the second parameter of the otl_generic_variable
type. Any template instantiated variables or arrays may be substituted
as actual parameters into those bind finctions.
class otl_generic_variable{
public: