XQJ Part VIII - Binding external variables

September 23, 2007 Data & AI

Last month in, XQJ Part III - Executing queries, we showed through some simple examples how to bind a value to an external variable declared in your query. In this post of the XQJ series, we will get into some more details on this subject.

As we know, XQuery operates on the abstract, logical structure of XML, known as the XQuery Data Model (XDM). As such, by definition in XQuery, the value bound to an external variable is an XDM instance. Having a Java object in your Java application, how is itconverted into such XDM instance? XQJ defines this mapping and glues it all together.

A first simple example,

[cc lang="java"] ... XQPreparedExpression xqp; XQSequence xqs; xqp = xqc.prepareExpression( "declare variable $id as xs:integer external; " + "doc('orders.xml')//order[id=$id]"); xqp.bindObject(new QName("id"),new Integer(174), null); xqs = xqp.executeQuery(); ...[/cc]

The bindObject() method is defined in the XQDynamicContext interface. It provides a number of methods to bind values to external variables. As XQDynamicContext is both the base for XQExpression and XQPreparedExpression, as such both expression implementations support binding values to external variables.
The first argument to the bindObject() method is a QName, which identifies the external variable in your XQuery. Second argument is the Java object to be bound and XQJ defines a mapping of Java objects to XDM instances. Providing the full list is out of scope, I would like to refer to the XQJ spec if you’re interested in all the details, but here a couple of examples,.nobrtable br { display: none }

Java type XQuery type
java.lang.Integer xs:int
java.lang.BigInteger xs:integer
java.lang.BigDecimal xs:decimal
java.lang.String xs:untypedAtomic
org.w3c.dom.Document untyped document node
org.w3c.dom.Element untyped element node
... ...

The third argument, for which null is specified in the example above, allows to override the default mapping. This is shown next [1].

[cc lang="java"] ... XQItemType xsinteger; xsinteger = xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER);

XQPreparedExpression xqp; XQSequence xqs;

xqp = xqc.prepareExpression( "declare variable $v1 external; " + "declare variable $v2 external; " + "$v1 instance of xs:integer, "+ "$v1 instance of xs:int, "+ "$v2 instance of xs:integer, "+ "$v2 instance of xs:int"); xqp.bindObject(new QName("v1"),new Integer(174), null); xqp.bindObject(new QName("v2"),new Integer(174), xsinteger); ...[/cc]

This example yields a sequence of 4 xs:boolean instances, [cc lang="java"] true, true, true, false[/cc]A Java Integer is by default mapped to xs:int. xs:int extends by restriction xs:integer, as such the first two 'instance of' expressions evaluate to true. The second external variable is bound with an xs:integer instance as the application explicitly specifies to create such XDM instance. As such the last 'instance of' evaluates to false, as xs:integer is not extending xs:int.

Note that various error conditions can occur during the binding process,

  • The conversion from Java to XDM instance can fail.
    For example, a java.lang.Integer object with value 10000 is converted into a xs:byte. As 10000 is out of bounds of the xq:byte value space, an error will be reported
  • Once converted into an XDM instance, the binding can still fail in case the external variable declaration includes a declared type. In such scenario the XDM instance must match the declared type according to the rules of SequenceType matching.
    For example, a java.lang.Integer is bound and converted into an xs:integer instance, but the external variable is declared as xs:string.

We have introduced the bindObject() method through some examples, but XQDynamicContext has many more bind methods.

bindAtomicValue() accepts a java.lang.String and will convert it to the specified type according to the casting rules from xs:string, basically the specified string must be in the lexical space of the specified atomic type. In the following example the Java String "123" is converted into xs:string, xs:integer and xs:double instances and bound the the external variables $v1, $v2 and $v3.

[cc lang="java"] ... xqp.bindAtomicValue(new QName("v1"), "123", xqc.createAtomicType(XQItemType.XQBASETYPE_STRING)); xqp.bindAtomicValue(new QName("v2"), "123", xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER)); xqp.bindAtomicValue(new QName("v3"), "123", xqc.createAtomicType(XQItemType.XQBASETYPE_DOUBLE)); ...[/cc]

In contrast, the following two bindAtomicValue() invocations will fail. The first because "abc" is not in the value spaces of xs:integer. The second one because no type has been specified as third parameter, unlike with bindObject(), bindAtomicValue() has no default mapping and a XQItemType must be specified as third argument.

[cc lang="java"] ... xqp.bindAtomicValue(new QName("e"), "abc", xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER)); xqp.bindAtomicValue(new QName("e"), "123", null); ...[/cc]

Further XQDynamicContext also provides bindXXX() methods for each of the Java primitive types,

  • boolean
  • byte
  • double
  • float
  • int
  • long
  • short

For example, binding an xs:integer instance 123 to the external variable $v. The default mapping for int is xs:int, as such we specify the type as third parameter.

[cc lang="java"] xqp.bindInt(new QName("v"), 123, xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER));[/cc]

Further binding a DOM node is also possible, basically the is equivalent to bindObject, with the restriction that the argument must be a DOM node and as such the XDM instance is always a node, never an atomic value. Of course in addition to DOM, also the SAX and StAX APIs are supported through XQDynamicContext.

Let’s read an XML document foo.xml through DOM, SAX and StAX and each time bind it to an external variable $v.

The DOM version,

[cc lang="java"] ... DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true);

DocumentBuilder parser = factory.newDocumentBuilder(); Document domDocument = parser.parse("foo.xml"); xqp.bindNode(new QName("e"), domDocument,null); ...[/cc]

The StAX version,

[cc lang="java"] ... XMLInputFactory factory = XMLInputFactory.newInstance(); FileInputStream doc = new FileInputStream("foo.xml"); XMLStreamReader reader = factory.createXMLStreamReader(doc); xqp.bindDocument(new QName("e"), reader, null); ...[/cc]

And the SAX version, for which we need to implement an XML Filter.

[cc lang="java"] ... XMLFilter xmlReader = new XMLFilterImpl() { public void parse(String systemId) throws IOException, SAXException { super.parse("foo.xml"); } }; // the parent XML Reader is a SAX parser, this one will do the actual // work of parsing the XML document xmlReader.setParent(org.xml.sax.helpers.XMLReaderFactory.createXMLReader()); xqp.bindDocument (new QName("e"), xmlReader); ...[/cc]

But of course, this is something you only want to use in specific scenarios. The simple use case of binding an xml file, can easily be accomplished in a single line. The XQJ implementation will make sure the XML file is parsed and queried.

[cc lang="java"] ... xqp.bindDocument(new QName("e"), new FileInputStream("foo.xml")); ...[/cc]

An XQItem or a complete XQSequence can also be bound to an external variable. We’ll discuss this soon in this XQJ series, in a post on pipelining. Talking about pipelining, XQJ also supports the JAXP Source and Result interfaces, these too will be discussed.

[1] In this series we have not yet introduced the createAtomicType() method defined on XQDataFactory. This will be handled in the next post. Anyway, for now it’s sufficient to know that it returns an XQItemType object representing the specified atomic type.

digg_skin = 'compact'; digg_url = 'http://www.xml-connection.com/2007/09/xqj-part-viii-binding-external.html';

XQJ

Marc Van Cappellen