On this page
- Introduction
- Connection handling
- Executing commands
- Data Definition Language (DDL)
- Storing data
- Transactions
- Retrieving data
- Executing PL/SQL blocks
- Use-case examples
- Insert table row and retrieve sequence value
- Retrieving particular table row
- Calling a stored procedure
- Calling a function in a package
- Alternatives
- Some final words
- Disclaimer
Introduction
This page will present some basic use-cases for OraLib - a lightweight
C++ wrapper over Oracle's OCI library. My intention was to code an easy-to-use
yet feature-full C++ library.
A note about resource ownership: almost all classes have release
method and it is expected method to be called when object instance is no longer
needed by the user code. There are cases, where release body is empty,
but this could change in the future. release method in not called
on all of the code examples below.
Pre-requisites: basic C++, SQL, PL/SQL knowedge.
In order to run samples you will also need Microsoft OS (Windows 9x, NT or 2000),
Microsoft Visual C++ compiler 6.0, STLport 4.0.x or 4.5.x, Oracle 8.1.6 and up.
I suppose it is possible to run the library on Linux/Unix, but I do not have the knowedge
(nor patience and time) to do it yet.
What is OraLib? OraLib is a C++ library, wrapper over Oracle's OCI library.
OraLib saves you low-level function (API) calls and low-level knowedge you should
otherwise posess. With OraLib you can easily do the following:
- connect/disconnect an Oracle server;
- execute DDL commands;
- execute SQL select/insert/update/delete commands;
- work with transactions;
- call packages/stored procedures or execute anonymous PL/SQL blocks;
- work with (named) bound variables;
- use build-in error handling by using C++ exceptions.
Following topics will present you various use-cases for OraLib.
Connection handling
There are two ways to connect to an Oracle server:
specify server name, login and password as a parameters to connection
object's constructor or by creating a blank object instance and calling
open method later. In either case an error will be thrown
if connect fails.
oralib::connection cn ("MyOracleServer", "MyLogin", "MyPassword");
...
cn.close ();
oralib::connection cn ();
cn.open ("MyOracleServer", "MyLogin", "MyPassword");
...
cn.close ();
Connection could be explicitly closed by calling close method
or implicitly, when object is deleted or goes out of scope.
In first case object instance could be reused to connect to the same or
another Oracle instance at later time.
Executing commands
Data Definition Language (DDL)
Execution of DDL commands is the simpliest case. connection object's
execute method could be called explicitly with SQL string to be executed.
cn.execute ("create table a (id numeric, name varchar2 (10))");
...
cn.execute ("drop table a");
Storing data
The easiest way to store data in an Oracle database is to use SQL insert
statement. A more complicated case is by calling a stored procedure, but generally
there are two cases: (1) data to be stored could be inside SQL statement - as text -
and, (2) data to be stored could be passed via bound variables (or parameters).
Both approaches have advantages and disadvantages. In first case you should build
a text string containing SQL insert statement (or stored procedure name)
along with fully formatted data values - for example by calling printf.
Second approach requires SQL insert statement to include bound variables
names only (or it will always be a constant) and to manually bind named variables
and set their values without worying about formatting.
Here is an example for both:
long id_column;
TCHAR *name_column;
char sql [100];
sprintf (sql, "insert into a (id, name) values (%d, '%s')",
id_column, name_column);
cn.execute (sql);
statement &st = *cn.prepare ("insert into a values (:n, :s)");
st.bind (":n") = id_column;
st.bind (":s") = name_column;
st.execute ();
st.release ();
Second approach is the better one, because: (1) Unicode text data
could be used - OCI expects SQL statements to be in an ANSI string
and Unicode text doesn't fit in ANSI character set;
wchar_t *name_in_unicode;
...
statement &st = cn.prepare ("insert into a (name) values (:s)");
st.bind (":s") = name_in_unicode;
st.execute ();
(2) multiple SQL insert statements
(where only insert values are different) could be executed sequentially
(this is also much faster compared to the 1st approach).
statement &st = *cn.prepare ("insert into a (id) values (:n)");
parameter &p = st.bind (":n");
for (long i=0; i<1000; i++)
{
p = i;
st.execute ();
}
Transactions
Normally in Oracle first data-related SQL statement creates an implicit
transaction. For example "insert into a (id) values (1)"
creates a transaction that should be explicitly closed (commited or rolled-back)
ot it will be closed implicitly when the connection is closed.
Until the transaction is closed the change made is visible only from within
the very same connection and in some cases other connections could be blocked.
connection object provides two methods for transactions handling:
commit and rollback. If you read the source code, you will find-out
that both are nothing more than simple calls to connection.execute.
Anyway, you should concider that transaction should be closed ASAP, because
a contention could occur - either by calling one of connection.commit /
connection.rollback or by including commit / rollback
in your stored procedures.
Retrieving data
There are two options when data should be retrieved. The choice depends on
how much data you wish to retrieve. When required data is a flag or count for example,
named variables could be used. But if you need to fetch a rows of data you should
use cursors (resultsets).
The usage of named variables for data retrieval is similar to their use
for storing of data:
statement &st = *cn.prepare (
"begin select count (id) into :n from a; end;");
st.bind (":n");
st.execute ();
num_rows = st [":n"];
Approach is suitable for cases where you wish to use a same named variable
for both input and output.
To fetch data from an explicit SQL select statement call either
connection.select or statement.select, depending whether you need
to supply some input data (select condition for example).
resultset &rs = *cn.select ("select name from a");
if (!rs.eod ())
do
cout << (Pstr) rs ["NAME"] << '\n';
while (++rs);
rs.release ();
statement &st = *cn.prepare ("select id, name from a where id = :n");
st.bind (":n") = id_required;
resultset &rs1 = *st.select ();
cout << '#' << (long) rs1 ["ID"] << ' ' << rs1 [2].as_string ();
rs1.release ();
When SQL select statement is executed and resultset object
is returned, columns could be accessed in two ways: (1) by name (case sensitive)
and (2) by index (whether index is 0- or 1-based is configured in oralib.h).
If you need to execute more then one SQL select statement then
cursor bound variables should be used (described in the following section).
Executing PL/SQL blocks
One of powerful features of Oracle database is PL/SQL. By using OraLib
you can execute PL/SQL blocks, pass input parameters and receive output parameters.
Output parameters can even be resultsets (cursor in Oracle docs).
Following example will execute two SQL select statements and will fetch
rows by using cursor named variables:
statement &st = *cn.prepare (
"begin\n"
" open :c1 for select id, name from a;\n"
" open :c2 for select * from a;\n"
"end;");
st.bind (":c1");
st.bind (":c2");
st.execute ();
resultset &rs = st [":c1"];
column &id_column = st [":c1"].as_resultset () ["ID"];
column &name_column = rs ["NAME"];
if (!rs.eod ())
do
cout << '#' << (long) id_column << ' '
<< (Pstr) name_column << '\n';
while (++rs);
name_column.release ();
id_column.release ();
rs.release ();
...
resultset columns could be accessed by asking the resultset
every time we need column's value or by caching it in a column object.
Second approach is faster, of course, but since late binding is being used,
statement should be executed first.
Use-case examples
Insert table row and retrieve sequence value
Oracle uses the concept of sequences to allow simultaneous inserts
in a single table (Microsoft SQL Server uses autonumber columns).
Because almost every modern system is used by more than one user at a time,
"select max (id) from a_table"-way is definately wrong.
But actually retrieving newly created row's id column is easy:
statement &st = *cn.prepare (
"begin\n"
" insert into a (id, name) values (a_seq.nextval, :s);\n"
" :n := a_seq.currval;\n"
" commit;\n"
"end;");
st.bind (":s") = name_column;
st.bind (":n");
st.execute ();
cout << "newly created row's id = " << (long) st [":n"];
st.release ();
Of course this should be placed into a stored procedure.
Retrieving particular table row
statement &st = *cn.prepare (
"select col1, col2, col3 from table_name where id = :n");
st.bind (":n") = id_we_re_looking_for;
resultset &rs = *st.select ();
...
rs.release ();
st.release ();
Calling a stored procedure
statement &st = *cn.prepare (
"begin sp_name (:param1, :param2, :param3); end;");
st.bind (":param1", DT_TYPE) = param1_value;
st.bind (":param2", DT_TYPE) = param2_value;
st.bind (":param3", DT_TYPE) = param3_value;
st.execute ();
...
st.release ();
Calling a function in a package
statement &st = *cn.prepare (
"begin :result := package_name.function_name ("
":param1, :param2, :param3); end;");
st.bind (":param1", DT_TYPE) = param1_value;
st.bind (":param2", DT_TYPE) = param2_value;
st.bind (":param3", DT_TYPE) = param3_value;
st.bind (":result", DT_TYPE);
st.execute ();
...
st.release ();
Alternatives
OraLib includes only a few classes (6 to be exact), but supports lots of
powerful features provided by OCI library: named variables, output cursors,
PL/SQL blocks execution. Library is distributed with full source code.
OCI will took you 50 lines of formatted code to start with in order
to connect an Oracle server. Not to mention "simple" things, like executing
a select statement and fetching the result or binding a named variable.
OO4O for C++ is simply a COM wrapper. OCCI comes with the newer Oracle
versions (9 and up), but it looks like that source code is unavailable.
There are other similar projects available - go
here for a bigger list.
Some final words
Hope you like it. Comments, feedback and requests are welcome.
Disclaimer
This software comes with no warranty. Use at your own risk.
Include library name and my e-mail in your projects. Notify me.