SQL Processor

1. Introduction

The SQL processor provides an XML interface to any SQL database accessible through JDBC. It allows to easily query databases and produce XML outputs readily usable by other processors. Conversely, the SQL processor allows to perform updates, insertions and deletions from XML data generated by other processors.

2. Inputs and Outputs

Type Name Purpose Mandatory
Input config Configuration in XML-SQL Yes
Input data Source XML data No
Input datasource Datasource configuration No
Output data Result XML data No

If the data input is not connected, the SQL processor configuration must not use XPath expressions operating on it. If this condition is not satisfied, the SQL processor generates an error at runtime. It is typically useful to omit the data input when the SQL processor only reads data from the database and generates an output XML document.

If the data output is not connected, the output of the SQL processor is simply ignored. It is typically useful to omit the data output when the SQL processor only updates or insert data into the database from an input XML document.

3. XML-SQL

3.1 Configuration

XML-SQL is a simple set of XML tags that allow you to integrate SQL queries and XML. XML-SQL lives in the http://orbeon.org/oxf/xml/sql namespace, which is usually mapped to the sql prefix. An XML-SQL configuration input has the following format:

  <sql:config xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <!-- Optional user content -->
  <sql:connection>
  <!-- Datasource name (can also be specified with optional "datasource" input) -->
  <sql:datasource>my-datasource</sql:datasource>
  <!-- ... -->
  </sql:connection>
  <!-- Optional user content -->
  </sql:config>

The sql:datasource element specifies a datasource name under java:comp/env/jdbc. In the example above, the datasource named my-datasource is used. How the datasource is configured depends on the application server used. Please refer to the documentation of your application server for more information.

Alternatively, the sql:datasource element can be omitted. In that case, the datasource input of the SQL processor must be connected to an external datasource definition, which describes database connections without using JNDI names mapped by the container. This is an example of datasource definition:

  <datasource>
  <!-- Specify the driver for the database -->
  <driver-class-name>org.hsqldb.jdbcDriver</driver-class-name>
  <!-- This causes the use of the embedded database -->
  <uri>jdbc:hsqldb:file:orbeondb</uri>
  <!-- Optional username and password -->
  <username>sa</username>
  <password/>
  </datasource>

Warning
External datasource definitions do not use connection pooling at the moment. Because creating database connections is usually an expensive operation, they should be used only for development or demo purposes.

The sql:config element can contain any number of user-defined content before and after the sql:connection element.

3.2 sql:execute

The sql:execute element controls the execution of a single SQL query or update (update, insert or delete). It must start with either a sql:query or a sql:update element, which contains the SQL to execute. sql:query can be followed by sql:results, sql:no-results or both elements. Any number of sql:execute elements can be used in order to execute several queries or updates within a single connection declaration.

  <!-- Optional user content -->
  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:query>
  <!-- ... -->
  </sql:query>
  <!-- Optional user content -->
  <sql:results>
  <!-- ... -->
  </sql:results>
  <sql:no-results>
  <!-- ... -->
  </sql:no-results>
  </sql:execute>
  <!-- Optional user content -->
  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
  <!-- ... -->
  </sql:update>
  </sql:execute>
  <!-- Optional user content -->

3.3 sql:query and sql:update

sql:query and sql:update encapsulate SQL statements. Like in JDBC, a distinction is made between queries (SQL select) and updates (SQL update, insert and delete):

  <sql:query xmlns:sql="http://orbeon.org/oxf/xml/sql">select * from employee</sql:query>

  <sql:update xmlns:sql="http://orbeon.org/oxf/xml/sql">insert into employees values ('John', 'Doe')</sql:update>

It is possible to pass parameters to a query using the sql:param element. sql:param requires a type attribute that specifies the type of the parameter to set. The type system is borrowed from the XML Schema Part 2: Datatypes specification. The following types are supported:

  • xs:string
  • xs:int
  • xs:boolean
  • xs:decimal
  • xs:float
  • xs:double
  • xs:dateTime
  • xs:date
  • xs:base64Binary
  • oxf:xmlFragment

xs:date and xs:dateTime should be in one of the following formats:

  • CCYY-MM-DDThh:mm:ss.sss
  • CCYY-MM-DDThh:mm:ss
  • CCYY-MM-DD

Unless a getter is nested in the sql:param element (see the section about nested queries below), a select attribute is mandatory. Its content is evaluated as an XPath expression against the input XML document:

  <sql:query xmlns:sql="http://orbeon.org/oxf/xml/sql">
select first_name, last_name from employee where employee_id in (<sql:param type="xs:int" select="/query/employee-id[1]"/>,<sql:param type="xs:int" select="/query/employee-id[2]"/>)
  </sql:query>

Any number of sql:param elements may be used.

sql:param supports an optional boolean replace attribute, that can take the value true or false (the default). When replace is set to true, the parameter is simply replaced in the query, instead of being set on a JDBC PreparedStatement. This however works only with the xs:int and oxf:literalString types. This attribute is useful to dynamically generate parts of SQL queries, or to set parameters that do not allow being set via JDBC's setYyy methods. For example, with SQL Server:

  <sql:query xmlns:sql="http://orbeon.org/oxf/xml/sql">
select top <sql:param type="xs:int" select="/query/max-rows" replace="true"/> * from employee
  </sql:query>

sql:param supports an optional separator attribute. When that attribute is present, the result of the XPath expression in the select attribute is interpreted as a node-set. One query parameter is set for each element in the node set, separated by the characters in the separator. For example:

  <sql:query xmlns:sql="http://orbeon.org/oxf/xml/sql">
select * from book where book_id in (<sql:param type="xs:int" select="/query/book-id" separator=","/>)
  </sql:query>

Assuming the input document contains:

  <query>
  <book-id>5</book-id>
  <book-id>7</book-id>
  <book-id>11</book-id>
  <book-id>13</book-id>
  </query>

The following equivalent query will be executed:

  <sql:query xmlns:sql="http://orbeon.org/oxf/xml/sql">
select * from book where book_id in (5, 7, 11, 13)
  </sql:query>

3.4 Root Element

It is important to make sure that one and exactly one root element is output by the SQL processor. A good place to put such a root element is around the sql:connection element:

  <sql:config xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <employees>
  <sql:connection>
  <!-- ... -->
  </sql:connection>
  </employees>
  </sql:config>

3.5 sql:results and sql:no-results

These elements must be used only in conjunction with sql:query. When a query return at least one row, the subsequent sql:results is evaluated. When no row is returned, sql:no-results is evaluated.

sql:results must contain a sql:row-results element, which is evaluated once for every row of the result set. sql:row-results typically contains user-defined content, as well as column getters such as sql:get-column and sql:get-columns.

  <sql:results xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:row-results>
  <employee>
  <first-name>
  <sql:get-column type="xs:string" column="first_name"/>
  </first-name>
  <last-name>
  <sql:get-column type="xs:string" column="last_name"/>
  </last-name>
  </employee>
  </sql:row-results>
  </sql:results>

Assuming the result set contains two rows with "John Doe" and "Billy Smith", the above code produces the following XML fragment:

  <employee>
  <first-name>John</first-name>
  <last-name>Doe</last-name>
  </employee>
  <employee>
  <first-name>Billy</first-name>
  <last-name>Smith</last-name>
  </employee>

3.6 Getting Column Values

You can get column values with the sql:get-column and sql:get-columns elements.

sql:get-column takes a mandatory column attribute and a mandatory type attribute. The type system is borrowed from the XML Schema Part 2: Datatypes specification. The following types are supported:

  • xs:string
  • xs:int
  • xs:boolean
  • xs:decimal
  • xs:float
  • xs:double
  • xs:dateTime
  • xs:date
  • xs:base64Binary
  • oxf:xmlFragment

xs:dateTime returns a date in the following format: CCYY-MM-DDThh:mm:ss.sss.

xs:date returns a date in the following format: CCYY-MM-DD.

oxf:xmlFragment is a special type that gets the column as a string, parses it as an XML fragment, and embeds the resulting XML in the SQL processor output.

Note
For compatibility with XPath 1.0, xs:float and xs:double do not return values in the exponential notation. For example, instead of 1.2E10, 12000000000 is returned.

When the number of columns returned is large, it is convenient to use sql:get-columns, which automatically determines what columns are available and generates elements accordingly. sql:get-columns takes an optional prefix attribute specifying the output namespace prefix to use for all the elements, and an optional format attribute specifying how column names are converted. It also supports any number of embedded exclude elements that specify columns to exclude from the result.

sql:get-columns supports an all-elements attribute. If set to true, an element is output for a column even if that column returns a null value. If missing or set to false, no element is output for a null column.

The namespace prefix, if specified, must have been mapped to a namespace URI.

If no format is specified, the original column names are used. Specifying the xml format converts all column names to lower case and transforms "_" into "-".

The example below generates the same results as above:

  <sql:results xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:row-results>
  <employee>
  <sql:get-columns format="xml"/>
  </employee>
  </sql:row-results>
  </sql:results>

Not specifying the xml format generates the following results:

  <employee>
  <first_name>John</first_name>
  <last_name>Doe</last_name>
  </employee>
  <employee>
  <first_name>Billy</first_name>
  <last_name>Smith</last_name>
  </employee>

It is possible to exclude the first_name column as follows:

  <sql:results xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:row-results>
  <employee>
  <sql:get-columns format="xml">
  <exclude>first_name</exclude>
  </sql:get-columns>
  </employee>
  </sql:row-results>
  </sql:results>

This generates the following results:

  <employee>
  <last_name>Doe</last_name>
  </employee>
  <employee>
  <last_name>Smith</last_name>
  </employee>

When no rows are returned, the content of the sql:results element is completely ignored. Instead, sql:no-results is evaluated. sql:no-results may contain user-defined elements and nested queries (see below).

3.7 Specifying a SQL Type

When setting a parameter of type xs:string or oxf:xmlFragment, it is possible to specify an additional attribute on sql:param: sql-type. By default, text is written using the JDBC setString() method. In case the data must be stored in the database as a Character Large OBject (CLOB) or other database-specific types, it is necessary to tell PresentationServer that a different API must be used. For example, to write a string into a CLOB:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_clob_table (clob_column) values (<sql:param select="/document/text" type="xs:string" sql-type="clob"/>)
  </sql:update>
  </sql:execute>

The same string can be written as a regular varchar type as follows:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_table (varchar_column) values (<sql:param select="/document/text" type="xs:string" sql-type="varchar"/>)
  </sql:update>
  </sql:execute>

varchar is actually the default, so you can simply omit the sql-type and write:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_table (varchar_column) values (<sql:param select="/document/text" type="xs:string"/>)
  </sql:update>
  </sql:execute>

Note
The disadvantage of using database columns of type varchar is that those are severely limited in size, for example 4000 bytes in the case of Oracle 9. The maximum size of CLOB columns is usually much larger, for example up to 4 GB with Oracle 9. In order to store large strings or large XML documents, it is therefore necessary to use the CLOB type.

The following values are supported for sql-type:

  • varchar (the default)
  • clob
  • xmltype (see Reading and Writing XML Documents below)
Warning

Using the clob and xmltype SQL types is currently only supported with the following application server / database combinations:

  • Tomcat 4.1 and Oracle 9
  • WebLogic 8.1 and Oracle 9

Please contact Orbeon to inquire about supporting additional systems.

Reading from CLOB columns on the other hand is supported with all JDBC drivers that support the CLOB API.

3.8 Reading and Writing XML Documents

As explained in the section about sql-type above, when text data, in particular XML data, is large, it is best stored as a CLOB type or, in the case of XML, as a native database XML data type such as the Oracle XMLType data type.

Reading and writing XML data is supported to and from database CLOBs and, with Oracle 9, to and from XMLType. The oxf:xmlFragment type must be used. To write XML data to a CLOB, use the oxf:xmlFragment type as follows:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_clob_table (clob_column) values (<sql:param select="/*" type="oxf:xmlFragment" sql-type="clob"/>)
  </sql:update>
  </sql:execute>

The XPath expression must return one element node. The result of the XPath expression specified in the select attribute is converted into a new XML document having as root element the selected element node. The document is then serialized to a character stream and stored as a CLOB.

To read a document from a CLOB column, use the oxf:xmlFragment type as follows:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:query>
select clob_column from test_clob_table
  </sql:query>
  <sql:results>
  <rows>
  <sql:row-results>
  <row>
  <sql:get-column type="oxf:xmlFragment" column="clob_column"/>
  </row>
  </sql:row-results>
  </rows>
  </sql:results>
  </sql:execute>

For each row returned, the character data stored in the CLOB column is read as text and parsed into an XML fragment. The fragment must be well-formed, otherwise an exception is thrown. The resulting fragment is then embedded into the SQL processor output.

With Oracle 9, it is also possible to write to the native Oracle XMLType data type:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_xmltype_table (xmltype_column) values (<sql:param select="/*" type="oxf:xmlFragment" sql-type="xmltype"/>)
  </sql:update>
  </sql:execute>

Note
The benefit of using the Oracle XMLType data type is that XML is stored in a structured way in the database. This allows creating indexes on XML data, doing partial document updates, etc. This however requires creating an XML schema. For more information, please refer to the Oracle XML DB Developer's Guide.

Reading from an XMLType column is done the same way as with a CLOB column:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:query>
select xmltype_column from test_xmltype_table
  </sql:query>
  <sql:results>
  <rows>
  <sql:row-results>
  <row>
  <sql:get-column type="oxf:xmlFragment" column="xmltype_column"/>
  </row>
  </sql:row-results>
  </rows>
  </sql:results>
  </sql:execute>

Warning

Writing to CLOB columns, as well as writing and reading to and from XMLType columns, is currently only supported with the following application server / database combinations:

  • Tomcat 4.1 and Oracle 9
  • WebLogic 8.1 and Oracle 9

Please contact Orbeon to inquire about supporting additional systems.

Reading from CLOB columns on the other hand is supported with all JDBC drivers that support the CLOB API.

3.9 Reading and Writing Binary Data

Reading and writing binary data is supported to and from database Binary Large OBjects (BLOBs) as well as binary types (BINARY, VARBINARY and LONGVARBINARY SQL types). The xs:base64Binary type (read and write) or the xs:anyURI type (write only) must be used. To write to a BLOB, use the xs:base64Binary type as follows:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_blob_table (blob_column) values (<sql:param select="/*" type="xs:base64Binary"/>)
  </sql:update>
  </sql:execute>

The result of the XPath expression specified in the select attribute is converted into a character string, following the XPath semantics. That string is then interpreted as Base64-encoded data, before being written to the BLOB column. For example, the following input document:

  <root>
/9j/4AAQSkZJRgABAQEBygHKAAD/2wBDAAQDAwQDAwQEBAQFBQQFBwsHBwYGBw4KCggLEA4R ... KKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//2Q==
  </root>

Is converted to the following string when the expression /* is applied:

/9j/4AAQSkZJRgABAQEBygHKAAD/2wBDAAQDAwQDAwQEBAQFBQQFBwsHBwYGBw4KCggLEA4R ... KKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//2Q==

With xs:anyURI, the result of the XPath expression is converted into a string and interpreted as a URL. The URL is read, and the resulting data is stored into the BLOB column. For example:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:update>
insert into test_blob_table (blob_column) values (<sql:param select="/my/uri" type="xs:anyURI"/>)
  </sql:update>
  </sql:execute>

Note
XForms file uploads typically generate URLs in XForms instances if the type chosen for the uploaded file in the XForms model is xs:anyURI. The advantage of using xs:anyURI is that large resources do not have to reside entirely in memory.

To read a BLOB or BINARY column, use the xs:base64Binary type as follows:

  <sql:execute xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:query>
select blob_column from test_blob_table
  </sql:query>
  <sql:results>
  <rows>
  <sql:row-results>
  <row>
  <sql:get-column type="xs:base64Binary" column="blob_column"/>
  </row>
  </sql:row-results>
  </rows>
  </sql:results>
  </sql:execute>

This will produce the following result if the document above was written to the database first:

  <rows>
  <row>
/9j/4AAQSkZJRgABAQEBygHKAAD/2wBDAAQDAwQDAwQEBAQFBQQFBwsHBwYGBw4KCggLEA4R ... KKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//2Q==
  </row>
  </rows>

Note

Base64-encoded binary documents are widely used in PresentationServer, in particular in the following cases:

  • Request generator: as the result of certain types of HTML form submissions (typically file uploads) or request body submission, Base64-encoded representation of the uploaded files may be stored into the request document.

  • XForms upload: as the result of a file upload, a Base64-encoded representation of the uploaded file may be stored into the XForms instance.

  • URL generator: can read binary documents and produce Base64-encoded output according to the standard binary document format.

  • HTTP serializer: can convert Base64-encoded input according to the binary document format into a binary stream.

  • SQL processor: as described in this document, it is able to read and write Base64-encoded binaries.

Warning

Writing to BLOB columns is currently only supported with the following application server / database combinations:

  • Tomcat 4.1 and Oracle 9
  • WebLogic 8.1 and Oracle 9

Please contact Orbeon to inquire about supporting additional systems.

Reading from BLOB columns on the other hand is supported with all JDBC drivers that support the BLOB API.

3.10 sql:value-of and sql:copy-of

The sql:value-of and sql:copy-of elements have the same semantics as their XSLT 1.0 couterparts. They work against the SQL processor's input XML document.

Those elements support functions in the http://orbeon.org/oxf/xml/sql namespace. The only function supported for the moment is sql:row-position(), which returns, in a sql:row-results, the index of the current row in the result set, starting with row number 1. For example:

  <sql:results xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <sql:row-results>
  <employee>
  <position>
  <sql:value-of select="sql:row-position()"/>
  </position>
  <first-name>
  <sql:get-column type="xs:string" column="first_name"/>
  </first-name>
  <last-name>
  <sql:get-column type="xs:string" column="last_name"/>
  </last-name>
  </employee>
  </sql:row-results>
  </sql:results>

This generates the following results:

  <employee>
  <position>1</position>
  <first-name>John</first-name>
  <last-name>Doe</last-name>
  </employee>
  <employee>
  <position>2</position>
  <first-name>Billy</first-name>
  <last-name>Smith</last-name>
  </employee>

3.11 Multiple Updates

The sql:update element supports an optional select attribute. It is evaluated as an XPath expression against the input XML document. The expression must return a node-set (which may be empty). The update statement is executed once for every node returned. select attributes on nested sql:param elements are evaluated using the selected node as current node. With the following input XML document:

  <employees>
  <employee>
  <first-name>John</first-name>
  <last-name>Doe</last-name>
  </employee>
  <employee>
  <first-name>Billy</first-name>
  <last-name>Smith</last-name>
  </employee>
  </employees>

The following update inserts two rows in the database:

  <sql:update select="/employees/employee" xmlns:sql="http://orbeon.org/oxf/xml/sql">
insert into employee (first_name, last_name) values (<sql:param type="xs:string" select="first-name"/>,<sql:param type="xs:string" select="last-name"/>)
  </sql:update>

3.12 Nested queries

Consider the following three SQL tables organized in a tree. The level2 table references the level1 table, and the level3 table references the level2 table.

level1

level1_id value
1 a
2 b

level2

level2_id level1_id value
1 1 a
2 1 b
3 2 c
4 2 d

level3

level2_id value
1 a
1 b
2 c
2 d
3 e
3 f
4 g
4 h

A flat representation of the three tables joined on their respective foreign keys yields the following rows:

level1.value level2.value level3.value
a a a
a a b
a b c
a b d
b c e
b c f
b d g
b d h

Often it is useful to group results in order to output certain values only once. In a table, this can look like the following:

level1.value level2.value level3.value
a a a
b
b c
d
b c e
f
d g
h

A generalization of this consists in generating output of the form:

  <result>
  <group1>
  <group1-header>
  <level1-value>a</level1-value>
  </group1-header>
  <group1-members>
  <group2>
  <group2-header>
  <level2-value>a</level2-value>
  </group2-header>
  <group2-members>
  <level3-value>a</level3-value>
  <level3-value>b</level3-value>
  </group2-members>
  <group2-footer>
  <level2-value>a</level2-value>
  </group2-footer>
  </group2>
  <group2>
  <group2-header>
  <level2-value>b</level2-value>
  </group2-header>
  <group2-members>
  <level3-value>c</level3-value>
  <level3-value>d</level3-value>
  </group2-members>
  <group2-footer>
  <level2-value>b</level2-value>
  </group2-footer>
  </group2>
  </group1-members>
  <group1-footer>
  <level1-value>a</level1-value>
  </group1-footer>
  </group1>
  <group1>
  <group1-header>
  <level1-value>b</level1-value>
  </group1-header>
  <group1-members>
  <group2>
  <group2-header>
  <level2-value>c</level2-value>
  </group2-header>
  <group2-members>
  <level3-value>e</level3-value>
  <level3-value>f</level3-value>
  </group2-members>
  <group2-footer>
  <level2-value>c</level2-value>
  </group2-footer>
  </group2>
  <group2>
  <group2-header>
  <level2-value>d</level2-value>
  </group2-header>
  <group2-members>
  <level3-value>g</level3-value>
  <level3-value>h</level3-value>
  </group2-members>
  <group2-footer>
  <level2-value>d</level2-value>
  </group2-footer>
  </group2>
  </group1-members>
  <group1-footer>
  <level1-value>b</level1-value>
  </group1-footer>
  </group1>
  </result>

There are two ways of generating such results with XML-SQL. The first way is to use nested queries. A first query returns all the rows in the level1 table. Then, for each row returned, a second query returns all rows in the level2 table referencing the current row's level1_id. Similarly, for each row returned by that second query, a new query is done to get the relevant level3 rows. The code would look as follows:

  <sql:config xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <results>
  <sql:connection>
  <sql:datasource>my-datasource</sql:datasource>
  <sql:execute>
  <sql:query>
select level1.value value, level1.level1_id id from level1 order by level1.value
  </sql:query>
  <sql:results>
  <sql:row-results>
  <group1>
  <group1-header>
  <level1-value>
  <sql:get-column type="xs:string" column="value"/>
  </level1-value>
  </group1-header>
  <group1-members>
  <sql:execute>
  <sql:query>
select level2.value value, level2.level2_id id from level2 where level2.level1_id =
<sql:param type="xs:int"><sql:get-column type="xs:int" column="id"/></sql:param>
order by level2.value
  </sql:query>
  <sql:results>
  <sql:row-results>
  <group2>
  <group2-header>
  <level2-value>
  <sql:get-column type="xs:string" column="value"/>
  </level2-value>
  </group2-header>
  <group2-members>
  <sql:execute>
  <sql:query>
select level3.value value from level3 where level3.level2_id =
<sql:param type="xs:int"><sql:get-column type="xs:int" column="id"/></sql:param>
order by level3.value
  </sql:query>
  <sql:results>
  <sql:row-results>
  <level3-value>
  <sql:get-column type="xs:string" column="value"/>
  </level3-value>
  </sql:row-results>
  </sql:results>
  </sql:execute>
  </group2-members>
  <group2-footer>
  <level2-value>
  <sql:get-column type="xs:string" column="value"/>
  </level2-value>
  </group2-footer>
  </group2>
  </sql:row-results>
  </sql:results>
  </sql:execute>
  </group1-members>
  <group1-footer>
  <level1-value>
  <sql:get-column type="xs:string" column="value"/>
  </level1-value>
  </group1-footer>
  </group1>
  </sql:row-results>
  </sql:results>
  </sql:execute>
  </sql:connection>
  </results>
  </sql:config>

A nested query can access parameters from the input XML document like any regular query. It can also access results from outer queries by nesting a getter in a sql:param element. In that case, getters can take an optional ancestor attribute that specifies which level of outer query to access. If omitted, the ancestor attribute takes the value 1 when used in a sql:query or sql:update, which means the first outer query; it defaults to the value 0 when used in a sql:row-results, which means the query at the current level.

3.13 Grouping

While nested queries have their uses, in the example above 3 queries have to be written and no less than 7 queries are executed to produce the final result. It can be elegantly rewritten using the sql:group and sql:member elements. sql:group has to be the first element under a sql:row-results or sql:member element. It takes a mandatory column attribute that specifies the name of the column on which grouping is done.

For every group, a header is output only once. Then, the content under the sql:member element is output for each row. Finally, the footer is output. The header and the footer can access columns There is no limit in XML-SQL as to how deep grouping can be done.

The code below generates with a single SQL query the same results as the example above:

  <sql:config xmlns:sql="http://orbeon.org/oxf/xml/sql">
  <results>
  <sql:connection>
  <sql:datasource>my-datasource</sql:datasource>
  <sql:execute>
  <sql:query>
select level1.value v1, level2.value v2, level3.value v3 from level1, level2, level3 where level1.level1_id = level2.level1_id and level2.level2_id = level3.level2_id order by level1.value, level2.value, level3.value
  </sql:query>
  <sql:results>
  <sql:row-results>
  <sql:group column="v1">
  <group1>
  <group1-header>
  <level1-value>
  <sql:get-column type="xs:string" column="v1"/>
  </level1-value>
  </group1-header>
  <group1-members>
  <sql:member>
  <sql:group column="v2">
  <group2>
  <group2-header>
  <level2-value>
  <sql:get-column type="xs:string" column="v2"/>
  </level2-value>
  </group2-header>
  <group2-members>
  <sql:member>
  <level3-value>
  <sql:get-column type="xs:string" column="v3"/>
  </level3-value>
  </sql:member>
  </group2-members>
  <group2-footer>
  <level2-value>
  <sql:get-column type="xs:string" column="v2"/>
  </level2-value>
  </group2-footer>
  </group2>
  </sql:group>
  </sql:member>
  </group1-members>
  <group1-footer>
  <level1-value>
  <sql:get-column type="xs:string" column="v1"/>
  </level1-value>
  </group1-footer>
  </group1>
  </sql:group>
  </sql:row-results>
  </sql:results>
  </sql:execute>
  </sql:connection>
  </results>
  </sql:config>

Note that correct ordering of the rows in the SQL query is important because headers and footers are output when the columns on which grouping is done change, in the order returned by the result set.

3.14 Text and Whitespace Handling

Like in XSLT 1.0, text nodes in the XML-SQL document containing only whitespace characters are stripped. Text nodes that contain at least one non-whitespace character are not stripped and copied to the output.

To better control the output of text, the sql:text element is provided. It is similar to the xsl:text element. xsl:text encapsulate text that is output as is. In particular, it can encapsulate all whitespace characters.

4. Transactions

PresentationServer executes each HTTP request in its own transaction. If a request fails for any reason, the SQL Processor rolls back the transaction. The transaction is committed only when the pipeline execution in complete.