Skip Headers

Oracle® Database Lite Oracle Lite Client Guide Release 10.3 Part Number E12548-02 |
Home | Book List | Contents | Index | Contact Us

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

- Section 12.1, "Getting Started With SODA"

- 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"

- Section 12.5, "Another Library for Exceptions (ALE)"

## 12.1 Getting Started With SODA

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

- Section 12.1.1, "Overview of the SODA Classes"

- Section 12.1.2, "Demonstrating Frequently-Used SODA 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:

- Section 12.5.1, "Decorating Classes With ALE"

- Section 12.5.2, "New Operator and ALE"

- Section 12.5.3, "Global Variables"

- Section 12.5.4, "Exceptions and Inheritance"

- Section 12.5.5, "Using ALE with PocketPC ARM Compilers"

- Section 12.5.6, "Troubleshooting ALE Runtime Errors"

- Section 12.5.7, "Compiling Your Program With ALE"

- Section 12.5.8, "ALE Code on Systems That Support Exceptions"

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