Oracle® Database Lite Oracle Lite Client Guide Release 10.3 Part Number E12548-02 |
|
|
View PDF |
SODA is an interface used in Oracle Database Lite C++ development that provides object-oriented data access using method calls, relational access using SQL and object-relational mapping to bridge the gap between the two. Object functionality is roughly three times faster than ODBC for simple operations. It enables rich datatypes—such as arrays and object pointers—as well as standard SQL columns. A programmer can store any data structure in the database and not think about relational design or performing joins.
A C++ developer can also use an interface for executing SQL statements. The resulting code is shorter and cleaner than ODBC. SQL queries can return objects to be examined and modified directly through the object-oriented layer, without calling any additional SQL statements.
Object-relational mapping enables the application to access relational data as if it was object hierarchy. Thus, your application can replicate rich data types or object pointers to the Oracle database server.
The SODA API method calls are documented in the SODA: Simple Object Data Access API Reference, which is located off the <ORACLE_HOME>
/Mobile/index.htm
page. The full sample code that is demonstrated in this chapter is located in the <ORACLE_HOME>
/Mobile/doc/soda/sodadoc/html/sodasimple_8cpp-source.html
file.
Section 12.2, "Using SQL Queries in SODA Code for PocketPC Platforms"
Section 12.3, "Virtual Columns and Object-Relational Mapping"
Section 12.4, "Behavior of Reference-Counted and Copy-By-Assignment Objects"
In order to get started with SODA quickly, the following sections discuss the most frequently used classes:
When developing your C++ application, you would use the following classes the most:
DBSession
connects to the database and find and create classes.
DBClass
creates new database objects.
DBObject
modifies existing objects.
DBData
wraps an attribute value and is used for type conversion.
DBQueryExpr
builds single-table queries supported by SODA.
DBString (olString)
is a C string wrapper used by SODA.
DBList (olList)
is a template to store lists of values.
DBColList
and DBDataList
instantiate these objects.
For example, implement the DBObject
method, as follows:
DBObject obj; ... obj["NAME"] = "Jack"
For full documentation for the SODA API method calls, see the SODA APIs, which you can find off the <ORACLE_HOME>
/Mobile/index.htm
page.
The following example demonstrates most of the SODA object-oriented functionality, as discussed in Section 12.1.1, "Overview of the SODA Classes".
void helloSODA() { puts("Hello SODA"); try { DBSession sess("POLITE"); // Connect to the DSN, creating it if necessary // Find or create our class DBClass cls; try { cls = sess["PEOPLE"]; } catch(DBException e) { cls = sess.createClass("PEOPLE", DBAttrList() << DBAttr("ID", DB_INT) << DBAttr("NAME", DB_STRING)); } // Create several objects. We can identify columns by name or positions DBObject o = cls.create("ID", 10, "NAME", "Alice"); // The previous syntax of col1, val1, col2, val2 ... works for up to // 32 columns. This version works for any number of columns cls.create(DBSetList() << "ID" << 10 << "NAME" << "Alice"); cls.create(0, 20, 1, "Bob"); // Note the automatic type conversion cls.create("ID", "314", 1, 3.14159265358); // Execute a query (will return two objects) DBCursor c = cls.createCursor(DBColumn("ID") == 10 || DBColumn("NAME") == "Bob"); DBObject ob; while (ob = ++c) { DBString s = ob["NAME"]; puts(s); o["ID"] = (int)o["ID"]+1; } // Delete an object o.remove(); // Clean up so that create class is successful next time sess.rollback(); } catch(DBException e) { DBString s = e.getMessage(); printf("Error: %s\n", (const char *)s); } }
To add SQL queries to your SODA code for PocketPC platforms, do the following:
Include sodasql.h
, instead of soda.h
.
Link your program with sodasql.lib
.
Install sodasql.dll
at runtime.
Create a DBSqlSession
object instead of the DBSession
object. Execute relational queries and other SQL statements with the help of DBSqlStmt
and DBSqlCursor
classes. Query results can be returned either as column values or as DBObjects
for matching rows.
The following is a sample that uses the SODA relational interface:
void helloSQL() { try { puts("Hello SQL"); DBSqlSession sess("POLITE"); sess.execute("create table odtest(c1 int, c2 varchar(80))"); DBSqlStmt stmt = sess.prepare("insert into odtest values(?,?)"); // The values stand in for two ?'s in the statement above stmt.execute(5, "John"); stmt.execute(10, "Mike"); stmt.execute(15, "Alice"); // Execute a single-table query that will return DBObject's for matching // rows. Use a standard SODA interface to access the objects DBSqlCursor c = sess.execute("odtest", "c1 < 15"); while (++c) { DBObject o = c.getObject(); DBString s = o["c2"]; int val = o["c1"]; printf("%d %s\n", val, (const char *)s); o["c1"] = val+1; // Can modify objects in addition to just reading them } // Execute a usual relational query c = sess.execute("select * from odtest"); while (++c) { DBString s = c["c2"]; printf("%d %s\n", (int)c["c1"], (const char *)s); } } catch(DBException e) { DBString s = e.getMessage(); printf("Error: %s\n", (const char *)s); } }
A programmer does not view the data as column values that are stored in the database. For example, a master-detail relationship might be expressed as matching values in two tables, but for a program it is more natural to access a column in the master object, which contains an array of pointers to details.
The DBVirtualCol
class enables the translation between the conceptual view of the data and the actual data in the tables. You can create a column that is completely under programmer's control through the get
, set
and remove
methods and adding it to a class at runtime.
In fact, SODA contains a specialized DBValueRel
class that extends the DBVirtualCol
class to map master-detail relationships to object pointers. The following sample builds a binary search tree in the database using object-relational mapping:
struct HelloVirtual { int lpos, rpos, vpos; void visit(DBObject o); HelloVirtual(); }; void HelloVirtual :: visit(DBObject o) { while (o) { visit(o[lpos]); printf("%d\n", (int)o[vpos]); o = o[rpos]; } } HelloVirtual :: HelloVirtual() { try { DBSession sess("POLITE"); // TreeNode represent a binary tree with left and right pointers // that point back to parent's id column DBClass cls = sess.createClass("TreeNode", DBAttrList() << DBAttr("val", DB_INT) << DBAttr("id", DB_INT) << DBAttr("lchild", DB_INT) << DBAttr("rchild", DB_INT)); // Create an index on id to speed up search cls.createIndex("i1", DBColList() << "id", true); // Set a sequence as a default value of id, so that we don't have to set it // explicitely DBSequence seq = sess.createSequence("s1"); cls.defaultVal("id", seq); // Create the virtual columns DBValueRel lref("left", DBSrcCol(cls, "lchild") -> DBDstCol(cls, "id"), DB_UPD_DETAIL); DBValueRel rref("right", DBSrcCol(cls, "rchild") -> DBDstCol(cls, "id"), DB_UPD_DETAIL); // Cache column positions for frequent access lpos = cls["left"], rpos = cls["right"], vpos = cls["val"]; // Root of the binary tree DBObject root; // Insert some random numbers into our binary search tree for (int i = 0; i < 50; i++) { int v = rand(); DBObject o = cls.create(vpos, v); // Note automatically generated ids DBObject par; int dir; for(DBObject cur = root; cur; par = cur, cur = cur[dir]) dir = (int)cur[vpos] >= v ? lpos : rpos; if (par) par[dir] = o; else root = o; } // Do in-order traversal of the tree, printing out numbers in sorted order visit(root); } catch(DBException e) { DBString s = e.getMessage(); puts(s); } }
Most C++ classes in SODA are reference-counted, which means that assigning one variable of one type to another cannot copy an object, but creates another way to refer to the same object. For example, the DBData
class represents values that can be stored in persistent objects.
The following example demonstrates reference-counting:
DBData d = 5; // Create a new object containing value 5 // and make a reference to it DBData d2 = d; // Both reference the same object d << 20; // Add another value to existing object. // Both d and d2 reference the new array d2 = 10; // d references the array, d2 is reassigned to the new data. d.clear(); // Clear the last reference to the array of 5 and 20 //and free the array
The programmer does not need to free objects when they are no longer used. However, this method is relatively expensive and not practical for objects that are created and destroyed often, such as when new lists of values, such as DBSetList
, are allocated for each SODA call. Various lists in SODA, such as DBSetList
, DBDataList
and so on, are copy-on-assignment rather than reference-counted objects.
The following example demonstrates copy-by-assignment:
DBSetList ls << "cost" << 1000; DBSetList ls2 = ls; // Created a copy of ls ls << "value" << "priceless" // Only ls is changed ls2.clear(); // Just set this copy to size 0
Note:
SODA includes non-database classes and templates for your convenience, which have names that start withol
rather than DB
—such as olHash
. Avoid making unnecessary copies of these classes.You can optimize your implementation by not creating an unnecessary copy by passing a reference or a const
reference to the object, rather than an object, for both reference-counted and copy-on-assignment classes when calling a function. For example, void func(const DBData &v)
avoids creating an unnecessary copy.
Note:
Theclear
method only nullifies a particular reference to reference-counted objects. DBSession
and DBCursor
classes provide a close
method that releases the underlying database resources, even while the objects are still referenced. Using anything that relies on a closed cursor or database connection throws a DBException
.Many embedded compilers, such as Visual C++ for PocketPC, do not support C++ exceptions. Oracle Database Lite includes ALE, which is a library that closely mimics C++ exceptions. The following sections describe how to use ALE:
A decorated class is one that uses ALE for handling its exceptions. If your embedded compiler does not support exception handling, then use ALE, which relies on careful accounting of all objects that are already constructed or are being constructed. ALE supports stack unwinding and catching exceptions based on object type.
To use ALE, C++ source code needs to be modified where the try, catch and throw blocks are replaced with the ALE macros, which is known as decorating classes. The following is an example of a decorated class:
#include "ale.h" struct Error { const char *msg; Error(const char *msg) :msg(msg) {} }; aleTry { olArray<char> a(5); aleThrow(Error,Error("Adios"));// Or aleThrowObj(Error,("Adios")); } aleCatch(Error,e) { puts(e.msg); } aleCatch(aleBadAlloc,e) { puts("Tough!"); } aleCatchAll { puts("Some other exception happened\n"); aleReThrow; } aleEnd;
Table 12-1 ALE Macros for C++ Exceptions
Macro | Action |
---|---|
|
Equivalent to C++ try. Use to enclose the code that might encounter any exception. |
|
Catch exception of a given type and store it in the variable |
|
Throw an exception contained in |
|
Construct a new object of |
|
Catch any exceptions that are not handled explicitly. |
|
Rethrow the exception that is caught in the innermost |
|
Close the exception handling construct. Add a semi-colon |
If your embedded compiler does not support exception handling, then use ALE, which relies on careful accounting of all objects that are already constructed or are being constructed. Your class is involved in exception handling if the class is on the stack when an exception is thrown, its constructor may throw an exception, or it has a decorated superclass and member—even if the class does not do any additional exception-related processing.
When you decorate one class with ALE, you must decorate all classes involved with this class. If you omit a decoration in one of the classes, then the program might fail. Therefore, it is best to decorate all your classes except for plain C-style structures that do not have any constructors or destructors.
If your class does not have any constructors with a body that throws exceptions (it is OK if the superclass or member constructor does), then you can use a simple form of class decoration by adding ALELAST(ClassName)
, without a semicolumn, at the end of class declaration. ALELAST
informs the library to register the class and clean-up if an error occurs. The following demonstrates how to use ALELAST
:
template<class T> class PtrHolder { T *ptr; public: ~PtrHolder() { delete ptr; } operator T *() { return ptr; } ALELAST(PtrHolder) };
If any of the constructors throw exceptions, then you need to do the following:
Add ALECLAST(ClassName)
, rather than ALELAST
to the end of the class body.
Add ALECONS(ClassName)
in the beginning of the body of each constructor.
This is demonstrated, as follows:
template<class T> class Array { T *a; size_t len; public: Array(size_t len=0} : len(len) { ALECONS(Array); a = new T[len]; } Array(const Array &arr) : len(arr.len) { ALECONS(Array); for (size_t i = 0; i < len; i++) a[i] = arr.a[i]; } ... ALECLAST(Array) };
In this example, the class contains an explicit copy constructor. If your class does not contain an explicit copy constructor and instances can be copied, then you need to explicitly write a copy constructor and add ALECONS(ClassName);
rather than using a copy constructor that is generated by the compiler. If you need a more complicated initialization than a default or copy constructor, then use a global pointer—which can be initialized by another global object, rather than a global instance.
Decorated classes can be safely used with the new
and delete
functions, including using new
and delete
for array
and placement new
. However, systems that do not support exceptions usually do not declare std::bad_alloc
and std::no_throw
types. Use aleBadAlloc
and aleNoThrow
instead of using std::bad_alloc
and std::no_throw
types.
One design decision is whether to decorate classes with constructors that only throw bad::alloc
if they run out of memory using ALELAST
or ALECLAST
. If you are writing classes for a single application that does not allocate much memory, you might dispense with error checking and just use ALELAST
. Your program might crash because of incorrect cleanup calls if it runs out of memory. If your class allocates a lot of memory or you are writing a highly-reusable framework, then it is best to use ALECLAST
and decorate all the constructors.
If you need a global or static variable to be decorated with ALE, declare it using aleGlobal
template, as follows:
aleGlobal<MyType> myGlobal; // Initialized with default constructor aleGlobal<MyType> myGlobal1(MyType("Hello", 5")); // Initialized with copy constructor. ... MyType *t = myGlobal; // Declared variables behave as pointers
Declare all global or static decorated instances using aleGlobal
or you may receive runtime errors.
Unlike regular C++ exception handling, ALE requires that class names in aleThrow
and aleCatch
match exactly. Typedef names, throwing a subclass, and catching a superclass will not work. To build a hierarchy of exceptions, add ALEPARENT
declaration to the subclass, as follows:
class BaseE { ALELAST(BaseE) }; class DerivedE : public BaseE { ALELAST(DerivedE) ALEPARENT(BaseE) };
DerivedE
can be caught as BaseE
. If you use multiple inheritance, then the first base class must be declared as a parent.
The Microsoft Embedded Visual C++ for ARM has a bug that is triggered when an ALE-decorated object (or any object with embedded pointers) is passed by value to a function or method. The affected code receives an ALE fatal error message at runtime. To avoid this problem, always pass SODA objects and instances of other classes that use ALE as a constant reference rather than value. For example, modify the following code:
void createName(DBClass cls, DBString name) { cls.create("name", name); }
to the following implementation:
void createName(const DBClass &cls, const DBString &name) { cls.create("name", name);
If your classes are not decorated properly, then you will receive runtime errors. On PocketPC, ALE displays a message box explaining the problem, and then the program terminates. In addition, the error and the dump of the ALE stack is appended to aleDump.txt
, which exists in the root directory of the device. In simple cases, the error message pinpoints the exact problem; for example ALECONS is missing for class MyArray
. Usually, one of the classes found near the top of the ALE stack is not decorated properly. If you do not decorate a class and its superclasses or members are decorated, then you may receive a runtime error and see the superclasses/members on the stack.
To build a program that uses ALE, include ale.h
from the Oracle Database Lite SDK and link with the olStdDll.lib
library. You need olStdDll.dll
at runtime.
For systems that already support C++ exceptions, like Win32, Oracle Lite includes a dummy ale.h
that defines the same macros, but uses regular C++ exceptions to implement them. If you are writing code that must execute on both Win32 and PocketPC, remember to test the code with the actual ALE library to ensure that all your classes are decorated correctly. ALELAST
, ALECLAST
or ALECONS
have no effect on the Win32 platform.