Examining Query Results

Examining Document Values
Examining Metadata
Copying Result Sets
Using Event Readers

When you perform a query against BDB XML, you receive a results set in the form of an XmlResults object. To examine the results, you iterate over this result set, retrieving each element of the set as an XmlValue object.

Once you have an individual result element, you can obtain the data encapsulated in the XmlValue object in a number of ways. For example, you can obtain the information as a string object using XmlValue.asString(). Alternatively, you could obtain the data as an XmlDocument object using XmlValue.asDocument().

It is also possible to use DOM-like navigation on the XmlValue object since that class offers navigational methods such as XmlValue.getFirstChild(), XmlValue.getNextSibling(), XmlValue.getAttributes(), and so forth. For details on these and other XmlValue attributes, see the BDB XML Javadoc.

For example, the following code fragment performs a query and then loops over the result set, obtaining and displaying the document's name from an XmlDocument object before displaying the document itself.

package dbxml.gettingStarted;

import com.sleepycat.dbxml.XmlContainer;
import com.sleepycat.dbxml.XmlException;
import com.sleepycat.dbxml.XmlManager;
import com.sleepycat.dbxml.XmlQueryContext;
import com.sleepycat.dbxml.XmlResults;
import com.sleepycat.dbxml.XmlValue;
...

XmlManager myManager = null;
XmlContainer myContainer = null;

...

try {
    // Get a manager object.
    myManager = new XmlManager();

    // Open a container
    myContainer = 
        myManager.openContainer("exampleData.dbxml");

    // Get a query context
    XmlQueryContext context = myManager.createQueryContext();

    // Declare a namespace
    context.setNamespace("fruits", "http://groceryItem.dbxml/fruits");

    // Declare the query string. Find all the product documents 
    // in the fruits namespace.
    String myQuery = "collection('exampleData.dbxml')/fruits:product";

    // Perform the query.
    XmlResults results = myManager.query(myQuery, context);

    // Show the size of the result set
    String message = "Found ";
    message += results.size() + " documents for query: '";
    message += myQuery + "'\n";
    System.out.println(message);

    // Display the result set
    XmlValue value = results.next();
    while (value != null) {
        XmlDocument theDoc = value.asDocument();
        String docName = theDoc.getName();
        String docString = value.asString();

        message = "Document ";
        message += theDoc.getName() + ":\n";
        message += value.asString();
        message += "===============================\n";

        System.out.println(message);
        value = results.next();
     }

     results.delete();
} catch (XmlException e) {
    // Error handling goes here.
} finally {
    try {
        if (myContainer != null) {
            myContainer.close();
        }

        if (myManager != null) {
            myManager.close();
        }
    } catch (XmlException ce) {
        // Exception handling goes here
    } 

Examining Document Values

It is frequently useful to retrieve a document from BDB XML and then perform follow-on queries to retrieve individual values from the document itself. You do this by creating and executing a query, except that you pass the specific XmlValue object that you want to query to the XmlQueryExpression.execute() method. You must then iterate over a result set exactly as you would when retrieving information from a container.

For example, suppose you have an address book product that manages individual contacts using XML documents such as:

<contact>
    <familiarName>John</familiarName>
    <surname>Doe</surname>
    <phone work="555 555 5555" home="555 666 777" />
    <address>
        <street>1122 Somewhere Lane</street>
        <city>Nowhere</city>
        <state>Minnesota</state>
        <zipcode>11111</zipcode>
    </address>
</contact>

Then you could retrieve individual documents and pull data off of them like this:

package dbxml.gettingStarted;

import com.sleepycat.dbxml.XmlContainer;
import com.sleepycat.dbxml.XmlException;
import com.sleepycat.dbxml.XmlManager;
import com.sleepycat.dbxml.XmlQueryContext;
import com.sleepycat.dbxml.XmlResults;
import com.sleepycat.dbxml.XmlValue;
...

XmlManager myManager = null;
XmlContainer myContainer = null;

...

try {
    // Get a manager object.
    myManager = new XmlManager();

    // Open a container
    myContainer = 
        myManager.openContainer("exampleData.dbxml");

    // Declare the query string. Retrieves all the documents
    // for people with the last name 'Doe'.
    String myQuery = "collection('exampleData.dbxml')/contact";

    // Query to get the familiar name from the
    // document.
    String fn = "distinct-values(/contact/familiarName)";

    // Query to get the surname from the
    // document.
    String sn = "distinct-values(/contact/surname)";

    // Work phone number
    String wrkPhone = "distinct-values(/contact/phone/@work)";

    // Get the context for the XmlManager query
    XmlQueryContext managerContext = myManager.createQueryContext();

    // Get a context for the document queries
    XmlQueryContext documentContext = myManager.createQueryContext();
   
    // Prepare the XmlManager query
    XmlQueryExpression managerQuery = 
        myManager.prepare(myQuery, managerContext);

    // Prepare the individual document queries
    XmlQueryExpression fnExpr = myManager.prepare(fn, documentContext);
    XmlQueryExpression snExpr = myManager.prepare(sn, documentContext);
    XmlQueryExpression wrkPhoneExpr = 
        myManager.prepare(wrkPhone, documentContext);

    // Perform the query.
    XmlResults results = managerQuery.execute(managerContext, 0);

    // Display the result set
    XmlValue value = results.next();
    while (value != null) {
        // Get the individual values
        XmlResults fnResults = fnExpr.execute(value, documentContext);
        XmlResults snResults = snExpr.execute(value, documentContext);
        XmlResults phoneResults = 
            wrkPhoneExpr.execute(value, documentContext);

        String fnString;
        if (fnResults.size() > 0) {
            XmlValue fnValue = fnResults.next();
            fnString = fnValue.asString();
        } else {
            continue;
        }

        String snString;
        if (snResults.size() > 0) {
            XmlValue snValue = snResults.next();
            snString = snValue.asString();
        } else {
            continue;
        }

        String phoneString;
        if (phoneResults.size() > 0) {
            XmlValue phoneValue = phoneResults.next();
            phoneString = phoneValue.asString();
        } else {
            continue;
        }

    String message = fnString + " " + snString + ": " + phoneString;
    System.out.println(message);
    value = results.next();

    results.delete();
    wrkPhoneExpr.delete();
    snExpr.delete();
    fnExpr.delete();
    managerQuery.delete();
} catch (XmlException e) {
    // Error handling goes here.
} finally {
    try {
        if (myContainer != null) {
            myContainer.close();
        }

        if (myManager != null) {
            myManager.close();
        }
    } catch (XmlException ce) {
        // Exception handling goes here
    } 

Note that you can use the same basic mechanism to pull information out of very long documents, except that in this case you need to maintain the query's focus; that is, the location in the document that the result set item is referencing. For example suppose you have a document with 2,000 contact nodes and you want to get the name attribute from some particular contact in the document.

There are several ways to perform this query. You could, for example, ask for the node based on the value of some other attribute or element in the node:

/document/contact[category='personal']

Or you could create a result set that holds all of the document's contact nodes:

/document/contact

Regardless of how you get your result set, you can then go ahead and query each value in the result set for information contained in the value. To do this:

  1. Iterate over the result set as normal.

  2. Query for document information as described above. However, in this case change the query so that you reference the self access. That is, for the surname query described above, you would use the following query instead so as to reference nodes relative to the current node (notice the self-access (.) in use in the following query):

    distinct-values(./surname)

Examining Metadata

When you retrieve a document from BDB XML, there are two ways to examine the metadata associated with that document. The first is to use XmlDocument.getMetaData(). Use this form if you want to examine the value for a specific metadata value.

The second way to examine metadata is to obtain an XmlMetaDataIterator object using XmlDocument.getMetaDataIterator(). You can use this mechanism to loop over and display every piece of metadata associated with the document.

For example:

package dbxml.gettingStarted;

import com.sleepycat.dbxml.XmlContainer;
import com.sleepycat.dbxml.XmlException;
import com.sleepycat.dbxml.XmlManager;
import com.sleepycat.dbxml.XmlMetaData;
import com.sleepycat.dbxml.XmlMetaDataIterator;
import com.sleepycat.dbxml.XmlQueryContext;
import com.sleepycat.dbxml.XmlResults;
import com.sleepycat.dbxml.XmlValue;
...

XmlManager myManager = null;
XmlContainer myContainer = null;

...

try {
    // Get a manager object.
    myManager = new XmlManager();

    // Open a container
    myContainer = 
        myManager.openContainer("exampleData.dbxml");

    // Get a query context
    XmlQueryContext context = myManager.createQueryContext();

    // Declare a namespace
    context.setNamespace("fruits", "http://groceryItem.dbxml/fruits");

    // Declare the query string. Find all the product documents 
    // in the fruits namespace.
    String myQuery = "collection('exampleData.dbxml')/fruits:product";

    // Perform the query.
    XmlResults results = myManager.query(myQuery, context);

    // Display the result set
    XmlValue value = results.next();
    while (value != null) {
        XmlDocument theDoc = value.asDocument();
        String docName = theDoc.getName();
        String docString = value.asString();


        // Display all of the metadata set for this document
        XmlMetaDataIterator mdi = theDoc.getMetaDataIterator();

        String message = "For document '" + theDoc.getName();
               message += "' found metadata:";
        System.out.println(message);

        XmlMetaData md = mdi.next();
        while (md != null) {
            message = "\tURI: " + md.get_uri();
            message += ", attribute name: " + md.get_name();
            message += ", value: " + md.get_value() + "\n";
            System.out.println(message);

            md = mdi.next();
        }

        // Display a single metadata value:
        String URI = "http://dbxmlExamples/timestamp";
        String attrName = "timeStamp";
        XmlValue newRetValue = null;

        boolean gotResult = theDoc.getMetaData(URI, attrName, newRetValue);
        if (gotResult) {
            message = "For URI: " + URI + ", and attribute: " + attrName;
            message += ", found: " + newRetValue + "\n";
            System.out.println(message);
        }

        message = "===============================\n";
        System.out.println(message);
        value = results.next();
     }

     results.delete();
} catch (XmlException e) {
    // Error handling goes here.
} finally {
    try {
        if (myContainer != null) {
            myContainer.close();
        }

        if (myManager != null) {
            myManager.close();
        }
    } catch (XmlException ce) {
        // Exception handling goes here
    } 

Copying Result Sets

When you create an XmlResults object by executing a query, the object actually references database objects. That is, the object is non-transient which means that if the objects in the database are modified or deleted in some way, then the contents of your XmlResults object can also be modified or deleted.

One way to guard against this is to use tansactions to provide isolation guarantees for your XmlResults objects. Transactions are described in the Berkeley DB XML Getting Started with Transaction Processing guide.

Another way to guard against this is to create a transient copy of your XmlResults object. You do this by using the XmlResults.copyResults() method. This method causes all of the XmlValue objects contained in the results set to no longer be references to database objects. As a result, you can safely use the result set outside of a transaction, and you can modify the copied results set without concern that you are modifying the container.

This method simply returns a new XmlResults object, which you can use in the same way you would use any XmlResults object.

...

XmlResults results = myManager.query(myQuery, context);
XmlResults transResults = results.copyResults(); 

...

It is also possible to concatenate two results sets together using the XmlResults.concatResults() method. This method is can only be used with transient results sets (that is, XmlResults objects created using the copyResults() method. In this case, the results set provided as an argument to the concatResults() method is concatenated to the XmlResults object that the method is called on.

...

XmlResults results1 = myManager.query(myQuery1, context);
XmlResults results2 = myManager.query(myQuery2, context);
XmlResults transResults1 = results1.copyResults(); 
XmlResults transResults2 = results2.copyResults(); 

// Concatenate results2 to results1
transResults1.concatResults(transResults2);

...

Using Event Readers

Once you have retrieved a document or node, you can examine that retrieved item using an event reader. Event readers provide a pull iterface that allows you to move through a document, or a portion of a document, using an iterator-style interface.

When you iterate over a document or node using an event reader, you are examing individual objects in the document. In this, the event reader behaves much like a SAX parser in that it allows you to discover what sort of information you are examining (for example, a start element, an end element, whitespace, characters, and so forth), and then retrieve relevant information about that data. (Note, however, that the event reader interface differs significantly from SAX in that SAX is a push interface while XmlEventReader is a pull interface.)

The document events for which you can test using the event reader are:

  • StartElement

  • EndElement

  • Characters

  • CDATA

  • Comment

  • Whitespace

  • StartDocument

  • EndDocument

  • StartEntityReference

  • EndEntityReference

  • ProcessingInstruction

  • DTD

In addition for testing for specific portions of a document, you can also retrieve information about those portions of the document. For example, if you are examining a starting element, you can retrieve the name of that element. You can also retrieve an attribute count on that element, and then retrieve information about each attribute based on it's indexed value in the start node. That is, suppose you have the following document stored in a container:

<a>
<b a1="one" b2="two">b node</b>
<c>c node</c>
</a> 

Then you can examine this document as follows:

try {
    // Container declaration and open omitted for brevity
    ...
    String dname = "doc1";
    XmlDocument doc1 = container.getDocument(dname);
    String content = doc1.getContentAsString();
    System.out.println("Doc: \n " + content);

    XmlDocument tdoc = container.getDocument(dname);
    // Get an XmlEventReader
    XmlEventReader reader = tdoc.getContentAsEventReader();

    // Now iterate over the document elements, examining only
    // those of interest to us:
    while (reader.hasNext()) {
        int type = reader.next();
        if (type == XmlEventReader.StartElement) {
            System.out.println("Found start node: " +
                reader.getLocalName());
            System.out.println("There are " + 
                reader.getAttributeCount()  +
                " attributes on this node.");
            // Show all the attributes on the start element node
            for (int i = 0; i < reader.getAttributeCount(); i++) {
                System.out.println("Attribute '" + 
                    reader.getAttributeLocalName(i) +
                    "' has a value of '" +  
                    reader.getAttributeValue(i) + "'");
            }
        }
    }

    // When we are done, we close the reader to free-up resources.
    reader.close();
} catch (XmlException xe) {
    System.err.println("Exception: " + xe.toString());
} 

Running this code fragment yields:

Found start node: a
There are 0 attributes on this node.
Found start node: b
There are 2 attributes on this node.
Attribute 'a1' has a value of 'one'
Attribute 'b2' has a value of 'two'
Found start node: c
There are 0 attributes on this node.

Note that you can also use event readers on XmlValue objects, provided that the object is an element node. For example:

try {
    // Container declaration and open omitted for brevity
    // As are the manager, query and XmlQueryContext 
    // declarations.
    ...
    while ((val = res.next()) != null) {

        if (val.isNode() &&
            (val.getNodeType() == XmlValue.ELEMENT_NODE)) {

            XmlEventReader reader = val.asEventReader();

            // Now iterate over the document elements, examining only
            // those of interest to us:
            while (reader.hasNext()) {
                int type = reader.next();
                if (type == XmlEventReader.StartElement) {
                    System.out.println("Found start node: " + 
                        reader.getLocalName());
                    System.out.println("There are " + 
                        reader.getAttributeCount() +
                        " attributes on this node.");

                    // Show all the attributes on the start element node
                    for (int i = 0; i < reader.getAttributeCount(); i++) {
                        System.out.println("Attribute '" + 
                            reader.getAttributeLocalName(i) +
                            "' has a value of '" + 
                            reader.getAttributeValue(i) + "'");
                    }
                }
            }
            // When we are done, we close the reader to free-up resources.
            reader.close();
        }
    }    

} catch (XmlException xe) {
    System.err.println("Exception: " + xe.toString());
}