Skip Headers
Oracle® Database Lite Oracle Lite Client Guide
Release 10.3

Part Number E12548-02
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

12 Using Simple Object Data Access (SODA)

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.

12.1 Getting Started With SODA

In order to get started with SODA quickly, the following sections discuss the most frequently used classes:

12.1.1 Overview of the SODA 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.

12.1.2 Demonstrating Frequently-Used SODA Classes

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);
 }
}

12.2 Using SQL Queries in SODA Code for PocketPC Platforms

To add SQL queries to your SODA code for PocketPC platforms, do the following:

  1. Include sodasql.h, instead of soda.h.

  2. Link your program with sodasql.lib.

  3. Install sodasql.dll at runtime.

  4. 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);
 }
}

12.3 Virtual Columns and Object-Relational Mapping

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);
 }
}

12.4 Behavior of Reference-Counted and Copy-By-Assignment Objects

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 with ol 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:

The clear 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.

12.5 Another Library for Exceptions (ALE)

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:

12.5.1 Decorating Classes With 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

aleTry

Equivalent to C++ try. Use to enclose the code that might encounter any exception.

aleCatch (type, varName)

Catch exception of a given type and store it in the variable varName, which is local to the block. Unlike the regular C++ exceptions, the type name string must match the argument of the throw exactly.

aleThrow (type, obj)

Throw an exception contained in obj of type. ALE supports single inheritence of exceptions, as described in Section 12.5.4, "Exceptions and Inheritance".

aleThrowObj (type, arg1, arg2, ....)

Construct a new object of type with the specified arguments and throw it as an exception.

aleCatchAll

Catch any exceptions that are not handled explicitly.

aleReThrow

Rethrow the exception that is caught in the innermost aleCatch or aleCatchAll block.

aleEnd

Close the exception handling construct. Add a semi-colon ; after aleEnd.


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:

  1. Add ALECLAST(ClassName), rather than ALELAST to the end of the class body.

  2. 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.

12.5.2 New Operator and ALE

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.

12.5.3 Global Variables

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.

12.5.4 Exceptions and Inheritance

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.

12.5.5 Using ALE with PocketPC ARM Compilers

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);

12.5.6 Troubleshooting ALE Runtime Errors

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.

12.5.7 Compiling Your Program With ALE

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.

12.5.8 ALE Code on Systems That Support Exceptions

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.