Chapter 3. XmlManager and Containers

Table of Contents

XmlManager
Berkeley DB Environments
XmlManager Instantiation and Destruction
Managing Containers
Container Properties
Container Types
Deleting and Renaming Containers

While containers are the mechanism that you use to store and manage XML documents, you use XmlManager objects to create and open XmlContainer objects. We therefore start with the XmlManager.

XmlManager

XmlManager is a high-level class used to manage many of the objects that you use in a BDB XML application. The following are some of the things you can do with XmlManager objects:

  • Manage containers. This management includes creating, opening, deleting, and renaming containers (see Managing Containers).

  • Create input streams used to load XML documents into containers (see Input Streams and Strings).

  • Create XmlDocument, XmlQueryContext, and XmlUpdateContext objects.

  • Prepare and run XQuery queries (see Using XQuery with BDB XML).

  • Create a transaction object (see the Berkeley DB XML Getting Started with Transaction Processing guide for details).

Because XmlManager is the only way to construct important BDB XML objects, it is central to your BDB XML application.

Berkeley DB Environments

Before you can instantiate an XmlManager object, you have to make some decisions about your Berkeley DB Environment. BDB XML requires you to use a database environment. You can use an environment explicitly, or you can allow the XmlManager constructor to manage the environment for you.

If you explicitly create an environment, then you can turn on important features in BDB XML such as logging, transactional support, and support for multithreaded and multiprocess applications. It also provides you with an on-disk location to store all of your application's containers.

If you allow the XmlManager constructor to implicitly create and/or open an environment for you, then the environment is only configured to allow multithreaded sharing of the environment and the underlying databases (DB_PRIVATE is used). All other features are not enabled for the environment.

The next several sections describe the things you need to know in order to create and open an environment explicitly. We start with this activity first because it is likely to be the first thing you will do for all but the most trivial of BDB XML applications.

Environment Open Flags

In order to use an environment, you must first open it. When you do this, there are a series of flags that you can optionally specify. These flags are bitwise or'd together, and they have the effect of enabling important subsystems (such as transactional support).

There are a great many environment open flags and these are described in the Berkeley DB documentation. However, there are a few that you are likely to want to use with your BDB XML application, so we describe them here:

  • DB_CREATE

    If the environment does not exist at the time that it is opened, then create it. It is an error to attempt to open a database environment that has not been created.

  • DB_INIT_LOCK

    Initializes the locking subsystem. This subsystem is used when an application employs multiple threads or processes that are concurrently reading and writing Berkeley DB databases. In this situation, the locking subsystem, along with a deadlock detector, helps to prevent concurrent readers/writers from interfering with each other.

    Remember that under the covers BDB XML containers are using Berkeley DB databases, so if you want your containers to be accessible by multiple threads and/or multiple processes, then you should enable this subsystem.

  • DB_INIT_LOG

    Initializes the logging subsystem. This subsystem is used for database recovery from application or system failures. For more information on normal and catastrophic recovery, see the Berkeley DB XML Getting Started with Transaction Processing guide.

  • DB_INIT_MPOOL

    Initializes the shared memory pool subsystem. This subsystem is required for multithreaded BDB XML applications, and it provides an in-memory cache that can be shared by all threads and processes participating in this environment.

  • DB_INIT_TXN

    Initializes the transaction subsystem. This subsystem provides atomicity for multiple database access operations. When transactions are in use, recovery is possible if an error condition occurs for any given operation within the transaction. If this subsystem is turned on, then the logging subsystem must also be turned on.

    We discuss writing transactional applications in the Berkeley DB XML Getting Started with Transaction Processing guide.

  • DB_RECOVER

    causes normal recovery to be run against the underlying database. Normal recovery ensures that the database files are consistent relative to the operations recorded in the log files. This is useful if, for example, your application experienced an ungraceful shut down and there is consequently an possibility that some write operations were not flushed to disk.

    Recovery can only be run if the logging subsystem is turned on. Also, recovery must only be run by a single thread of control; typically it is run by the application's master thread before any other database operations are performed.

Regardless of the flags you decide to set at creation time, it is important to use the same ones on all subsequent environment opens (the exception to this is DB_CREATE which is only required to create an environment). In particular, avoid using flags to open environments that were not used at creation time. This is because different subsystems require different data structures on disk, and it is therefore illegal to attempt to use subsystems that were not initialized when the environment was first created.

Opening and Closing Environments

To use an environment, you must first open it. At open time, you must identify the directory in which it resides and this directory must exist prior to the open attempt. At open time, you also specify the open flags, properties, if any, that you want to use for your environment.

When you are done with the environment, you must make sure it is closed. You can either do this explicitly, or you can have the XmlManager object do it for you.

If you are explicitly closing your environment, you must make sure any containers opened in the environment have been closed before you close your environment.

For information on XmlManager instantiation, see XmlManager Instantiation and Destruction

For example:

#include "DbXml.hpp"
...
u_int32_t env_flags = DB_CREATE     |  // If the environment does not
                                       // exist, create it.
                      DB_INIT_LOCK  |  // Initialize the locking subsystem
                      DB_INIT_LOG   |  // Initialize the logging subsystem
                      DB_INIT_MPOOL |  // Initialize the cache
                      DB_INIT_TXN;     // Initialize transactions
char *envHome = "/export1/testEnv";
DB_ENV *myEnv = NULL;
int dberr;

dberr = db_env_create(&myEnv, 0);
if (dberr) {
    std::cout << "Unable to create environment: " <<
              db_strerror(dberr) << std::endl;
    if (myEnv)
        myEnv->close(myEnv, 0);
    return (EXIT_FAILURE);
}

myEnv->open(myEnv, envHome, env_flags, 0);

// Do BDB XML work here


myEnv->close(myEnv, 0); 

XmlManager Instantiation and Destruction

You create an XmlManager object by calling its constructor. You destroy a XmlManager object by calling its destructor, either by using the delete operator or by allowing the object to go out of scope (if it was created on the stack). Note that XmlManager is closed and all of its resources released when the last open handle to the object is destroyed.

To construct an XmlManager object, you may or may not provide the constructor with an open DB_ENV object. If you do instantiate XmlManager with an opened environment handle, then XmlManager will close and destroy that DB_ENV object for you if you specify DBXML_ADOPT_DBENV for the XmlManager constructor.

If you provide an DB_ENV object to the constructor, then you can use that object to use whatever subsystems that you application may require (see Environment Open Flags for some common subsystems).

If you do not provide an environment object, then XmlManager will implicitly create an environment for you. In this case, the environment will not be configured to use any subsystems and it is only capable of being shared by multiple threads from within the same process. Also, in this case you can optionally identify the on-disk location where you want your containers to reside using one of the following mechanisms:

  • Specify the path to the on-disk location in the container's name.

  • Specify the environment's data location using the DB_HOME environment variable.

In either case, you can pass the XmlManager constructor a flag argument that controls that object's behavior with regard to the underlying containers (the flag is NOT passed directly to the underlying environment or databases). Valid values are:

  • DBXML_ALLOW_AUTO_OPEN

    When specified, XQuery queries that reference created but unopened containers will automatically cause the container to be opened for the duration of the query.

  • DBXML_ADOPT_DBENV

    When specified, XmlManager will close and destroy the DB_ENV object that it was instantiated with when the XmlManager is closed.

  • DBXML_ALLOW_EXTERNAL_ACCESS

    When specified, XQuery queries executed from inside BDB XML can access external sources (URLs, files, and so forth).

For example, to instantiate an XmlManager with a default environment:

#include "DbXml.hpp"

...

using namespace DbXml;
int main(void)
{
    XmlManager myManager;   // The manager and underlying
                            // environment are closed when 
                            // this goes out of scope.

    return(0);
} 

And to instantiate an XmlManager using an explicit environment object:

#include "DbXml.hpp"
...

using namespace DbXml;
int main(void)
{
    u_int32_t env_flags = DB_CREATE     |  // If the environment does not
                                           // exist, create it.
                          DB_INIT_LOCK  |  // Initialize locking
                          DB_INIT_LOG   |  // Initialize logging
                          DB_INIT_MPOOL |  // Initialize the cache
                          DB_INIT_TXN;     // Initialize transactions

    char *envHome = "/export1/testEnv";
    DB_ENV *myEnv = NULL;
    int dberr;

    XmlManager *myManager = NULL;

    dberr = db_env_create(&myEnv, 0);
    if (dberr) {
        std::cout << "Unable to create environment: " <<
                  db_strerror(dberr) << std::endl;
    if (myEnv)
        myEnv->close(myEnv, 0);
    return (EXIT_FAILURE);

    myEnv->open(myEnv, envHome, env_flags, 0);
    myManager = 
        new XmlManager(myEnv, 
                       DBXML_ADOPT_DBENV); // The manager and
                                           // environment is closed
                                           // when this object is
                                           // destroyed.

    try {
        if (myManager != NULL) {
            delete myManager;
        }
    } catch(XmlException &xe) {
        std::cerr << "Error closing XmlManager: "
                  << xe.what() << std::endl;
    }
}