Derived Elements

Server extensions are also useful in the area of derived fields. A derived field is an information element that is not stored within a document, but is computed when the document is retrieved. This is usually done to avoid redundant data within a document, and in cases when the value of a document node depends on the environment.

In XQuery, derived values can be generated by using constructors in the return clause. The remainder of this section describes how to deal with derived elements using X-Query.

A typical example is a person's age. We can store a person's date of birth in a document, but we cannot store his or her age because it changes over time. However, with server extensions we can define an age element in a document that describes a person, for example in jazzMusician. When such a document is retrieved, we must compute the value of this element by subtracting the date of birth from the current date.

Because we rely on values from other document nodes – here birthDate – during the computation of a derived field, we must make use of a callback function. There are three types of callback functions: XML, ODBC and System. It is the XML callback functions that we need, because we want to query Tamino for the birthDate element.

Here is the complete code for the server extension. The callback code is highlighted.

//      encyclopedia_jazzMusician.java: 
//         Implementation of server extension encyclopedia_jazzMusician: 

package tamino.SXS.jazz.encyclopedia.jazzMusician;
import com.softwareag.ino.sxs.*;
import java.util.Date;
import java.text.SimpleDateFormat; 
// Javadoc comments.
/**
 * All extensions for jazz database
 * @author Berthold Daum
 * @version 1.0
 */ 
public class encyclopedia_jazzMusician extends ASXJBase {
  /**
   * The default constructor
   * (No other constructor allowed.)
   */ 
        public encyclopedia_jazzMusician () {
        } 
        /**
         * compute age from birth date (map-out function)
         * @param object_id parameter
         * @param element_id parameter
         * @param document parameter
         */ 
  public void computeAge (int object_id, 
                          int element_id, 
                          StringBuffer document) 
              throws java.lang.Exception
  {
    // create query string for callback
    String xmlQuery = 
           "jazzMusician[@ino:id='"+object_id+"']/birthDate";
    // create new string buffer for call back results
    StringBuffer response = new StringBuffer(1024);
    // callback: XQL query
    int ret = SxsXMLXql ("encyclopedia", xmlQuery, response);
    // Here we dive deep into the rather complex error 
    // handling for server extensions callbacks.
    if (ret != 0) 
      {
        // identify the callback error
        int msgNo = SxsGetMsgNo (); 
        switch (msgNo) {
        case INO_ERROR:
          StringBuffer msgBuf = new StringBuffer ( );
          // Get the Tamino Server message number
          int inoMsgNo = SxsGetInoMsgNo ( );
          // Get the Tamino Server messageline
          ret = SxsXMLGetMessage (msgBuf);
           throw (new Exception("INO_ERROR: "
                                     +inoMsgNo+" "+msgBuf));
        default:
        // Get the SXS message corresponding to callback error
          String msg = SxsGetMsgText ();
          throw (new Exception("SXS Error: "+msgNo+" "+msg));  
        } 
      }  // error handling done, now the real logic
      // Remember, we had obtained the birthDate in "response"
      // Convert into string
      String  docstr = response.toString();
      // We have to isolate the value of the birthDate 
      //  element from the Tamino response string.
      // Could be properly done with SAX, 
      // but to be brief we do it by hand. 
      int t = docstr.indexOf("<birthDate", 0);
      // we have to scan for tag begin and end separately
      // because the tag includes attributes (ino:id).
      int b = docstr.indexOf('>',t+10)+1;
      // Find end of element
      int e = docstr.indexOf("</birthDate>", b);
      // Convert content into Date format
      SimpleDateFormat df = 
              new SimpleDateFormat ( "yyyy-MM-dd" );
      Date d1 = df.parse(docstr.substring(b,e-1));
      // Now extract years and months
      // These methods are deprecated, but we use them anyway.
      int y1 = d1.getYear();
      int m1 = d1.getMonth();
      Date d2 = new Date();
      int y2 = d2.getYear();
      int m2 = d2.getMonth();
      // Compute age
      int age = y2-y1;
      // Adjust age, if we are still 
      //   before this year's birthday
      if ((m1 > m2) || 
           ((m1 == m2) && (d1.getDate() > d2.getDate()) ))
                   age--;
      // Now pack into appropriate tags
      docstr = "<age>"+age+"</age>";
      // Write element to result buffer
      document.replace (0, docstr.length(), docstr); 
  // To delete computeAge, remove it here and from Install.xml.
  }
}

After the usual process – compilation, packing, and installation – we can use this server extension. First, we must define an element <age> in the corresponding jazzMusician schema:

graphics/sxs.png

The resulting schema fragment looks like this:

<xs:element name = "age" type = "xs:short">
  <xs:annotation>
    <xs:appinfo>
      <tsd:elementInfo>
        <tsd:physXNode>
          <tsd:mapXTension>
            <tsd:onCompose>
              encyclopedia_jazzMusician.computeAge
            </tsd:onCompose>
          </tsd:mapXTension>
        </tsd:physXNode>
      </tsd:elementInfo>
    </xs:appinfo>
  </xs:annotation>
</xs:element>

After making this schema known to the database, we can store jazzMusician document instances. These instances must include dummy <age> elements:

<age>0</age>

If we did not supply such elements (if we had defined minOccurs="0" in the element declaration), the computeAge server extension would not be called when the document is retrieved, and no age would be computed and displayed.

Tip:
If a server extension derives a value for an attribute rather than for an element, and a default value for the attribute is specified in the schema, it is not necessary to provide a dummy value for the attribute. This is because Tamino stores document instances with default attribute values if explicit attribute values are not provided.

Tip:
You can allow a default value to be generated for an element or an attribute by using a server extension Query function. Refer to the section tsd:default in the document Tamino XML Schema Reference Guide for details.

When we now query jazzMusician documents, the current age of each jazz musician is displayed. For example, the following query issued in October 2004:

jazzMusician[@ID="ColtraneJohn"]

results in:

<jazzMusician ino:id="1" type="instrumentalist"
              ID="ColtraneJohn">
  <name>
    <first>John</first> 
    <last>Coltrane</last> 
  </name>
  <birthDate>1926-09-23</birthDate> 
  <age>78</age> 
</jazzMusician>

The same query issued in October 2002 results in an age of 76.

The derived field can also be used in queries:

jazzMusician[age > 70]

However, because we cannot index derived fields, search criteria involving derived fields are always processed in the post-processing phase of a query (see From Schema to Tamino::Efficient Queries).