 | Level: Introductory Daya Vivek (vivekd@us.ibm.com), developer, pureQuery runtime team,
IBM
01 May 2008 IBM® pureQuery is a high-performance Java™ data access platform focused on simplifying the tasks of developing and managing applications that access data. It consists of tools, APIs and a runtime engine. pureQuery introduces two programming styles to help users access the database through simple but powerful APIs. This article introduces one such style, the inline method programming style, and discusses how users can use it to efficiently query and update databases. This article also explores the benefits as well as some of the key features of using the inline method programming style.
Introduction
pureQuery introduces two new programming styles for SQL execution: the inline method
programming style and the annotated method programming style. These styles simplify Java
data access development by providing out-of-the-box support for storing and retrieving objects, such as Java beans and maps, to and from the database. The inline programming style also supports the use of customized user-defined result processing.
The annotated method style provides an annotation-based approach for data access. In
the annotated method style, the SQL/XQUERY string is defined within a pureQuery Annotation. These annotations are
placed on method declarations within a user-defined interface. A code generator
pre-processes the interface to generate implementation code for each annotated method.
The generated implementation code executes the SQL statements defined in the annotation
using the pureQuery runtime. For more information on the annotated method style, refer
to Part
1 of this series or the pureQuery documentation.
The pureQuery inline programming style was introduced to reduce the repeated tasks that
are common to querying or updating a database using JDBC. The inline method style
introduces a set of well-defined and efficient APIs that are simpler and easier to use
than JDBC. In the inline style, SQL/XQUERY statements can be created “inline” within the
code as a java String object. This dynamically generated statement is passed as a String
parameter to the pureQuery Data
interface method. The inline style uses JDBC best practices and leverages
database-specific APIs to improve performance. An example of the inline method style is
the use of batch updates and optimized result-set processing. In contrast to the annotated style, where the SQL has to be defined at compile time, the inline style supports dynamic creation and execution of SQL statements at runtime. The SQL is “inline” and visible within the application, and this makes it is easier to spot errors and make corrections. The inline style also has a pluggable custom result processing for mapping database columns easily.
Table 1 provides a snippet of code to demonstrate the simplicity and usability of the inline method style.
Table 1. A code comparison between JDBC and the pureQuery inline style
|
JDBC
|
pureQuery inline style
|
try {
//SQL for insert
String sqlins="'insert into CUSTOMER ("
+ "NAME, COUNTRY, STREET, CITY, PROVINCE, ZIP,"
+ "PHONE,INFO)"
+ "values( ?, ?, ?, ?, ?, ?, ?, ?)";
//prepare the INSERT statement
PreparedStatement pstmt =
con.prepareStatement(sqlins );
// setup parameters
pstmt.setString (1, "custName");
pstmt.setString (2, "custCountry");
pstmt.setString (3, "custStreet");
pstmt.setString (4, "custCity");
pstmt.setString (5, "custProvince");
pstmt.setString (6, "custZip");
pstmt.setString (7, "custPhone");
pstmt.setString (8, "custInfo");
//execute the INSERT statement
pstmt.execute();
//close the prepared statement
pstmt.close();
// SQL for SELECT
String sqlSel = "select Name, Country, Street, "
+ "Province,Zip from CUSTOMER where Customer = ?";
//prepare the SELECT statement
pstmt = con.prepareStatement(sqlSel);
//set the Input parameter
pstmt.setString (1, "custCountry");
//execute SELECT statement
pstmt.execute();
//get the results and set values in Customer Bean
ResultSet result = pstmt.getResultSet ();
List<Customer> custList =
new ArrayList<Customer>();
while (result.next ()) {
Customer cust = new Customer();
cust.name = result.getString (1);
cust.country = result.getString (2);
cust.street = result.getString (3);
cust.province = result.getString (4);
cust.zip = result.getString (5);
custList.add (cust);
}
}
catch (SQLException e) {
e.pringStackTrace ();
}
|
|
//Get Instance of Data
Data data = DataFactory.getData(con);
// SQL for insert
String sqlins = "insert into CUSTOMER ("
+ "NAME,COUNTRY,STREET,CITY,PROVINCE,ZIP, "
+ "PHONE, INFO)"
+ "values( ?, ?, ?, ?, ?, ?, ?, ?)";
//execute the INSERT statement
data.update (sqlins, "custName",
"custCountry", "custStreet", "custCity",
"custProvince", "custZip", "custPhone", "custInfo");
// SQL for SELECT
String sqlSel = "select Name, Country, Street, "
+ "Province,Zip from CUSTOMER where Customer = ?";
//execute the Select and get the list of customer
List<Customer> customerList = data.queryList (sqlSel, Customer.
class , "custCountry");
|
|
This article includes:
-
A description of the various aspects of code required for the inline coding style
-
An introductory case study to highlight the features of the inline style
-
A description of the querying capabilities provided by the inline style, which includes pureQuery’s default mapping support and the customized query mapping support
-
A description of the single update and batch update support, and APIs to retrieve the auto-generated values
-
An introduction to pureQuery’s pluggable callback mechanism.
Developing inline methods
Described below are some of the different objects and APIs that a user may need to program an inline method style application:
-
The Data interface
The com.ibm.pdq.runtime.Data
interface defines convenience APIs for a user to perform operations on a Database. When
using the inline method programming style, a user can invoke methods defined in the Data
interface and pass the SQL statement as a parameter in the invocation of a method. The
Data interface provides methods to execute queries, execute SQL CALL statements and to return their output parameters. It also provides a means to access the dynamic result sets created by a stored procedure and to execute SQL data manipulation language (DML) statements such as update, insert or delete as single operations or in homogeneous batches. Many of the Data interface’s query methods are generic, so they can return instances of different types including collections.
-
The DataFactory Class
The com.ibm.pdq.runtime.factory.DataFactory
class provides a means for constructing implementations of the Data interface. One way
to create an implementation of the Data interface is to pass a Connection object to the DataFactory.getData() method.
-
pureQuery bean
A pureQuery bean can be used to represent relational data such as database tables,
views, result sets, and the like. Although pureQuery API methods can deal with input
parameters and return values defined as unstructured types, the use of the pureQuery
bean provides the user better mapping capabilities of relational data to Java objects.
pureQuery analyzes the declared properties, methods, and annotations of a bean to
determine how to map between the bean’s properties and the input/output properties of an
SQL statement. More information on the conventions and requirements of pureQuery beans can be found in the pureQuery documentation.
-
Java application with inline methods
This is the Java file where the user can create an instance of the Data interface and
call different query, update or call methods.
 |
The Silver
Castle example
This section introduces an example of how the data access development team in a
fictional company called Silver Castles uses pureQuery. This article uses the background information described here to provide compelling examples and use-cases for each pureQuery inline feature.
Background information for the Silver Castle example
Silver Castles is a growing company which sells a variety of silver products. The company is developing a new web-based storefront and has decided to use the pureQuery environment as a tool to develop the persistence layer of their application. Once the development team has obtained a solid understanding of the pureQuery tools environment, they begin to delve deeper into the technical aspects of the pureQuery inline coding style.
Sample inline method style program
The Silver Castle development team defined the Customer Table as below:
Listing 1. The Customer table as defined in the database
CREATE TABLE PDQ_SC.customer(
Cid INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (STARRT WITH 100, INCREMENT BY 1),
Name VARCHAR(128),
Country VARCHAR(128),
Street VARCHAR(128),
City VARCHAR(128),
Province VARCHAR(128),
Zip VARCHAR(128),
Phone VARCHAR(128),
Info VARCHAR(128),
CONSTRAINT CID_PK primary key(Cid));
|
The pureQuery bean for this Customer table can be generated using the Data Studio tool. That Customer bean is defined below:
Listing 2. The Customer
class
public class Customer {
// Class variables
@Id
@GeneratedKey
public int cid;
public String name;
public String country;
public String street;
public String city;
public String province;
public String zip;
public String phone;
public String info;
public Customer(){}
public Customer(String name, String country, String street, String city, String province,
String zip, String phone, String info){
this.name = name;
this.country = country;
this.street = street;
this.city = city;
this.province = province;
this.zip = zip;
this.phone = phone;
this.info = info;
}
}
|
Now, the Silver Castles development team tries to create a test program (Listing 3) to do the following:
-
Get a connection to the database
-
Get an instance to the implementation of a Data interface
-
Delete all the rows in the data base
-
Insert a row using a pureQuery bean as in input parameter
-
Query the database and retrieve rows in an iterator of pureQuery beans
Listing 3. Manipulating the data objects using the inline Method style
package com.ibm.db2.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Iterator;
import java.util.List;
import com.ibm.db2.pureQuery.Customer;
import com.ibm.pdq.runtime.Data;
import com.ibm.pdq.runtime.factory.DataFactory;
public class InlineTest
{
public static void main(String args[]) {
Connection con = null;
String sql = null;
String insertSql = null;
try {
// connection to the database
con = DriverManager.getConnection ();
// Get an instance to the implementation of a Data interface
Data data = DataFactory.getData (con);
//delete all rows from the table
data.update ("delete from PDQ_SC.customer");
//Insert using a pureQuery Bean
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street, City, " +
"Province, Zip, Phone)" +
"VALUES(:name,:country,:street,:city,:province,:zip,:phone)";
Customer addCustomer = new Customer("Customer2","US","BlackBerry Street",
"San Jose","Santa Clara","82652","408-273-4856",null);
int insertCount = data.update (insertSql,addCustomer );
System.out.println ("Rows Inserted " + insertCount );
sql = "select * from PDQ_SC.Customer where Country = ?";
//Query with Iterator using default handler
Iterator<Customer> iterator = data.queryIterator (sql, Customer.class, "US");
con.close ();
}
catch (Exception e) {
System.out.println ("Error encountered");
e.printStackTrace ();
}
}
public static Connection getConnection ()
{
Connection connection = null;
try {
Class.forName ("com.ibm.db2.jcc.DB2Driver").newInstance ();
java.util.Properties info = new java.util.Properties ();
info.put ("retrieveMessagesFromServerOnGetMessage", "true");
info.put ("user", "USER");
info.put ("password", "PASSWORD");
String url = "jdbc:db2://atom.blue.test.com:298/SAMPLE:deferPrepares=false;";
connection = DriverManager.getConnection (url, info);
}
catch (Exception e) {
e.printStackTrace ();
}
return connection;
}
}
|
 |
Creating an instance of the Data object
To create an instance of com.ibm.pdq.runtime.
Data,
a connection to a database is required in the form of either a java.sql.Connection
object or a javax.sql.DataSource object. Once a connection has been established, a user
can call a getData() method in com.ibm.pdq.runtime.factory.DataFactory
to create an instance of the Data interface. In addition to the methods for executing an
SQL statement, the Data interface supports the close(), commit(), rollback() and setAutoCommit() JDBC methods.
In Listing 3, the Silver Castle team uses the getConnection() method to create a database connection. This method uses the Driver Manager API to create this connection by passing a database connection URL String. The connection is then passed to the DataFactory.getData() method to get an instance of the Data interface.
Input parameter options
The Silver Castle developers may want to use any of the parameter markers or host variable styles provided by pureQuery. Their preference could be based on the number of input parameters or the types of input parameters to the inline method. The parameters that are passed into the method invocation are matched to the parameters in the SQL. pureQuery follows parameter marker rules to determine the mapping between the parameters declared in the SQL/XQUERY and the parameters that are passed to the inline method. In the simplest form, there is a one to one correspondence between the two sets of parameters. pureQuery can also take a variety of parameter types like pureQuery Beans or a java.util.Map.
Querying objects in a database
The inline method style provides convenience methods and provides out-of-the-box support for mapping database results to maps and pureQuery beans. The Data interface’s overloaded queryArray(), queryIterator(), and queryList() methods can be used to return the entire result set of a query as an Array, Iterator, or List object.
The query methods can return results as primitive types, beans, collections, and the
like, including:
-
java.sql.ResultSet,
-
Primitive wrapper types or simple Object types that are supported directly by JDBC (does not include user-defined types)
-
java.util.Map object, where column names become String keys, and column values become
Object values
-
java.util.Map objects grouped together into Arrays, Collections, or Iterators
-
Individual pureQuery beans, where a user can store the results of a query in a pureQuery Bean (the @Column annotations provide information needed to associate columns of the select-list with their respective properties; more details of pureQuery annotation can be found in the pureQuery documentation)
-
pureQuery beans grouped together into Arrays, Collections, or Iterators where the class of the return bean is passed in as a parameter
Once the target data type has been decided, the user needs to consider whether or not to use a custom handler to transform information selected from the data source into the target. Instances of pureQuery beans can be created either with pureQuery’s default mapping from a SELECT statement’s select-list to the target Bean class, or by using a user defined handler.
Querying the Silver Castle Customer table
The Silver Castle developers want to inform all the customers in Australia about an
upcoming special on the salt and pepper shaker sets. The common print utility methods
that print all the pamphlets take a java.util.List as input. The developer queries the
Customer table for all customers who reside in Australia and get the rows returned as a
List. They can use the queryList() API, and pass in the country as an input parameter.
The returned list (customerList in the example below) is then passed to the print
utility, so that the pamphlets get addressed to the correct set of customers on the list.
Listing 4. Querying the Customer table
sql = "select * from PDQ_SC.Customer where Country = ?";
//Query with List using default handler
List<Customer> customerList = data.queryList (sql, Customer.class, "Australia");
|
User-defined custom mapping of result sets
The RowHandler
and ResultHandler
interfaces gives users the ability to provide a customized user-defined mapping. pureQuery is told how to map the columns in a result set to a different Java object using the handle() method in either the RowHandler or ResultHandler interface. Row Handlers and Result Handlers allow the developer a great degree of flexibility, and the potential for code reuse because they are able to make use of a ResultSet’s ResultSetMetaData to handle variations in the select list. This can be used to include the omission of some properties or to specify properties that are not needed.
In some cases, an application developer might want some form of serialization to occur to the query’s result as a whole. For example, the only processing done against the query’s result could be serialization in JSON, or XML, or simply to send the data to another (or the same) data source. The user can use the ResultHandler to perform such custom operations.
Mapping one row of a result set
The RowHandler
interface is used to map one row of a result set to an object. The only method in the
RowHandler interface is handle(). This is a generic method that, given a ResultSet
object and optionally an instance of the class <T>, either produces a new Java
object of class <T>, or updates the given instance of <T>. If the ResultSet
object is empty, or the last row of the ResultSet object has been read, the handle()
method is not called. When the handle() method is called, the ResultSet object that is
passed to the method is positioned on the row to be processed. The handle() method must
not invoke the next() method on the input ResultSet object as pureQuery performs that action automatically.
Mapping a result set to an object
The ResultHandler interface is used to map an entire result set to an object. The user can use the handle() method in the ResultHandler interface to convert the query results to another object, say XML, that can be passed along to another application that takes XML as an input.
Updating objects in a database
The Data interface’s update() method can be used to perform a single update to a database object and the updateMany() method to perform a batch of updates that are based on a single SQL statement. The methods supported are INSERT, DELETE, and UPDATE operations.
Single updates
The update() method in the Data interface can be used for a single operation, which
returns an update count. Listing 5 is an example to a call to the overloaded method update() from the application
Listing 5.Application calling the overloaded method update()
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street, City, " +
"Province, Zip, Phone) VALUES (:name, :country, :street, " +
":city, :province,:zip,:phone)";
//Create an instance of Customer Bean with the values to be inserted
Customer addCustomer =
new Customer("Customer2","US","BlackBerry Street","San Jose",
"Santa Clara","82652","408-273-4856", null);
//Insert using a Bean
int updateCount = data.update (insertSql, addCustomer);
|
Batch updates
In the Data interface, the updateMany() method indicates that a statement is to be run multiple times. The updateMany() method allows passing a collection, or batch, of data to iterate through.
The returned int array indicates, as in JDBC, the success (and associated update count)
or failure of each run of the SQL statement. This includes use of JDBC’s update counts,
Statement.EXECUTE_FAILED and SUCCESS_NO_INFO. to indicate if failures occur -- a com.ibm.pdq.runtime.exception.UpdateManyException exception is thrown. This runtime exception contains information that JDBC reports in java.sql.BatchUpdateException.
Updating the Silver Castle Customer Table
Once every couple of months, a customer may contact the Silver Castle administration
team with an update to an address or phone number. The Silver Castle developers can update the table with the new information using the single update
API. Listing 6 shows the use of this update API.
Listing 6. Single update API to update table
//Update the Street name for Customer
String updateSql = "UPDATE PDQ_SC.customer SET Street= 'Townsend' WHERE name= ?";
int updateCount = data.update (updateSql,"Customer3");
System.out.println ("Rows Updated " + updateCount);
|
The Silver Castle team also has a website where new customers can sign up to have the
Silver Castle catalog mailed to them. Every month a utility is run to update the
Customer database to add these new customers. For this purpose, the developers could
create a job that can batch all the requests and insert them into the database by using
just one inline method. They could use the batch update API for this case to efficiently
update the database. Listing 7 shows the batch update API.
Listing 7. Batch update API
//Example Using UpdateMany
Customer addFirstCustomer =
new Customer("Customer3","Costa Rica","Main Street","Arenal",
"La Fortuna","90291","506-375-0273",null);
Customer addSecondCustomer =
new Customer("Customer4","Puerto Rico","Church Street",
"Puerto Nuevo","San Juan","38364","293-484-8244",null);
ArrayList<Customer> customerList = new ArrayList<Customer>();
customerList.add (addFirstCustomer);
customerList.add (addSecondCustomer);
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street, City, " +
"Province, Zip, Phone) VALUES (:name, :country, :street, " +
":city, :province,:zip,:phone)";
int[] updateCount = data.updateMany( sql, customerList);
if (updateCount != null)
System.out.println ("Rows Inserted:” + updateCount.length);
|
Retrieving auto-generated values
The overloading of the update() method allows the user to optionally request information regarding auto-generated columns. The Silver Castle developers can get the values generated by the database in any number of ways based on what the input to the update method is. Shown below are examples of the different ways the Silver Castle developers can code their application to get generated values back from the database after an insert.
Auto-generated values with pureQuery beans
In this example, the input to the insert is a pureQuery bean. The Customer object is
defined with an @GeneratedKey
annotation, and the column CID is defined as an identity column that always generates an
integer. The value in the CID column is passed into the CID property before control is
returned from the call to the update() method. Here, the auto-generated value is
set in the CID property of the customer object.
Listing 8. Get the auto-generated value in the CID
property of the Customer bean
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street, City, " +
"Province, Zip, Phone) VALUES (:name, :country, :street, " +
":city, :province,:zip,:phone)";
//Create an instance of Customer Bean with the values to be inserted
Customer customer =
new Customer("Customer2","US","BlackBerry Street","San Jose",
"Santa Clara","82652","408-273-4856", null);
//Insert using a Bean
int updateCount = data.update (insertSql, customer);
System.out.println ("Generated Key Value:" + customer.cid);
|
Auto-generated values without pureQuery Beans
The Silver Castle developer can pass values without a pureQuery bean and still retrieve auto-generated values by using the following version of the update() method:
<T> T update(java.lang.String sql, Class<T> returnClass,
String[] columnNames, Object... parameters)
|
This method returns one or more generated values, depending on the return type indicated. The value of the return type Class<T> must be either:
-
Object[].class
-
A simple class directly assignable from JDBC, such as Integer.class or String.class
When the return type is a simple, directly assignable JDBC class, a single generated
value is returned of the given type. The update count is not returned. Below is how a
Silver Castle developer could get the auto-generated value into a simple class (for
example, int.class).
Listing 9. Get the auto-generated value into a
simple class
Object[] customerArray = new Object[7];
customerArray[0] = "CustomerForGenKey";
customerArray[1] = "US";
customerArray[2] = "Bixby Street";
customerArray[3] = "San Martin";
customerArray[4] = "Santa Clara";
customerArray[5] = "62826";
customerArray[6] = "408-272-6565";
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street," +
"City, Province, Zip, Phone) VALUES(?,?,?,?,?,?,?)";
String[] colunmName = new String[] { "cid" };
int i = data.update (insertSql, int.class, colunmName, customerArray);
System.out.println ("AutoGenerated Keys as Int " + i + "\n");
|
When the return type is Object[].class, the first n elements of the array are
the n generated values from the columns in the columnName parameter. The last
element in the array is the update count. Below is how the Silver Castle developer could
get the generated key value using an Object[]. The auto-generated value is returned in Object[0]
and the update count is returned in Object[1] as Listing 10 shows.
Listing 10. Get the auto-generated value into an
Object[ ]
Object[] customerArray = new Object[7];
customerArray[0] = "CustomerForGenKey";
customerArray[1] = "US";
customerArray[2] = "Barnaby Street";
customerArray[3] = "Gilroy";
customerArray[4] = "Santa Clara";
customerArray[5] = "62823";
customerArray[6] = "408-273-6568";
insertSql = "INSERT INTO PDQ_SC.customer (Name, Country, Street," +
" City, Province, Zip, Phone) VALUES(?,?,?,?,?,?,?)";
String[] colunmName = new String[] { "cid" };
Object[] output = data.update (insertSql, Object[].class,
colunmName, customerArray);
|
A pluggable callback mechanism using the Hook interface
The com.ibm.pdq.runtime.Hook interface can be used to create methods that run before and after inline methods. Assume that the Silver Castle developers know that their products cannot be shipped to customers in certain countries. For the sake of this example, we can assume that the countries with shipping restrictions are Costa Rica and Australia. The Hook interface provides an easy way so that a developer can define this check once and not have to add code in multiple places to check the customer’s country each time the Customer table is updated.
Hooks must be registered to instances of Data when the instances are created. When the Data object that is used by inline methods has a Hook registered, the following steps occur when an application calls a method of the Data interface:
-
Before any work is done by a method of the Data interface, the pre() method of the registered Hook is invoked.
-
The method being called does its work.
-
After all work is done by the method, the post () method of the registered Hook is invoked.
The Data interface, which defines inline methods, contains a number of JDBC methods on Connection objects, such as close (), commit (), rollback (), setAutoCommit (), and getAutoCommit (). The pre () and post () methods of a registered hook do not bracket these methods.
The pre () and post () methods need to be defined using the following parameters
-
String methodName: pureQuery passes the signature of the method to the pre () method
-
Data dataInstance: pureQuery passes the object that implements the Data interface
-
SqlStatementType statementType: pureQuery passes the type of the SQL statement (for
example, SELECT, UPDATE, INSERT)
-
Object... parameters: pureQuery passes the parameters that are passed to the inline method
-
Object returnValue: return value from the call to the inline method
The example below demonstrates the use of the Hook interface. A Silver Castle application developer would define the pre() and post() method in the application under a class named HookCall. The pre() method checks whether the input parameter, the customer’s country, is valid and prevents an invalid row from being inserted into the database. Validation of the output from the method call can be checked using the post() method.
Listing 11. Use of the Hook interface
public static class HookCall implements Hook
{
public void pre (String methodName, Data objectInstance,
SqlStatementType statementType, Object... parameters)
{
String country = ((Customer)parameters[0]).country;
If (!statementType.equalsIgnoreCase ("update") ||
!statementType.equalsIgnoreCase("insert")) return;
if (!(country.equals ("Australia") || country.equals ("Costa Rica")) )
throw new DataRuntimeException
("This Country " + country + " is not part of our customer base");
}
public void post (String methodName, Data objectInstance,
Object retValue, SqlStatementType statementType,
Object... parameters)
{
//Do nothing
}
}
|
The application could register the hook as shown in Listing 12 below. The input value for the country property of the Customer bean will be validated when the pre() method gets invoked.
Listing 12. Registering the hook
Connection con = DriverManager.getConnection(...);
HookCall hookCall = new HookCall ();
Data db = DataFactory.getData(con, hookCall);
sql = "INSERT INTO PDQ_SC.customer (Name, Country, Street, City, Province, Zip, Phone)"
+ "VALUES(:name,:country,:street,:city,:province,:zip,:phone)";
Customer addCustomer2 = new Customer ("Customer2", "Costa Rica", "BlackBerry Street",
"San Jose", "Santa Clara", "82652","408-273-4856", null);
int insertCount = db.update (sql, addCustomer2);
|
Summary
This article provides a high-level introduction to the pureQuery inline method coding
style. It provides some examples to illustrate likely motivations for a development team to develop a pureQuery application using inline methods. It also outlines the basic steps required to develop an inline method style application. Several powerful features of the style are introduced as potential motivators for choosing to code using inline method programming style.
If you are interested in learning more about developing pureQuery inline style applications, please follow the links throughout the article and in the resources section to the pureQuery online documentation, additional articles, and helpful tutorials.
Resources
About the author  | 
|  | Daya Vivek is a developer on the pureQuery runtime team at the IBM Silicon Valley Lab in San Jose California. |
Rate this page
|  |