CachéRDD provides yet another powerful feature to execute SQL Statements. CachéRDD's implementation of SQL interface returns true server-side forward-cursors. This is important because of the fact that there is no memory overhead whatsoever on the client. For simplicity let us call it Query Interface, though this feature is not restricted to generate query only. Almost all of the SQL can be submitted for executuion.
Query interface necessarily involves these steps:
1.  Defining a SQL statement. 
2.  Building a parameters array, if any. 
3.  Preparing the defined statement. 
4.  Executing thus prepared statement. 
5.  Retreiving results, if any.
If everything goes ok and query executes successfully, application receives a cursor handle to traverse and retrieve the results.
A typical SQL statement could look like this...
 
SELECT ip.item_id, es.loc, el.item_name, el.cost, el.price, el.ordered, 
                el.inv_no, el.inv_date, el.cust_id, el.sub_acct, es.int_rate, es.no_of_pmt 
FROM itemprom ip 
LEFT OUTER JOIN estlines el ON ip.item_id=el.item_id 
LEFT OUTER JOIN estimate es ON el.inv_no=es.inv_no '
WHERE el.inv_date >= ? AND el.cust_id > ? AND es.posted <> ? 
ORDER BY el.inv_no
 
The character "?" represent the parameter which will be passed when executing the ststement. The parameters must be passed exactly in the same order appearing in the statement and number of paarmeters will be exactly the same. Otherwise query will return nothing. In example above there are 3 occurances of character "?" denoting that there must be three parameters to be passed with the execution method. Not only exact number and order is important but type of parameter must also be the same what query expects it to be.
CachéRDD exposes two approaches to exploit this powerful feature:
1. Functions based. 
2. Class based.
I will focus on Class based approach as it provides a clean environment hiding the complexity of functions always expecting correct number of parameters. This can be best illustrated with an example as follows.
//-------------------------------------------------------------------//
Function TestComplexQuery()
Local nNumParam , aTypeParam, aTypeSqlParam 
Local nNumCols, aTypeCols, aTypeSqlCols, aColNames
Local cSql := ''
Local aParams := {}
Local aResult := {}
Local hQry, oSql
/* The parameters to Process the SQL Statement */
Local cCustid := '500-0000'
Local dDate := ctod( '01/01/2006' )
Local cEmpty := ''
Local nConxn := CachéAddConnectionEx( "127.0.0.1", 1972, "_system", "SYS", 30, "USER" )
/* 
Build the SQL Statement 
*/
cSql += 'SELECT ip.item_id, es.loc, el.item_name, el.cost, el.price, '
cSql += 'el.ordered, el.inv_no, el.inv_date, el.cust_id, el.sub_acct, '
cSql += 'es.int_rate, es.no_of_pmt '
cSql += 'FROM itemprom ip LEFT OUTER JOIN estlines el ON ip.item_id=el.item_id '
cSql += 'LEFT OUTER JOIN estimate es ON el.inv_no=es.inv_no '
cSql += 'WHERE el.inv_date >= ? AND el.cust_id > ? AND es.posted <> ? '
cSql += 'ORDER BY el.inv_no'
aParams := { dDate, cCustID, cEmpty }
/* 
Create an SQL Query Object 
*/
oSql := CachéSqlQuery():New( nConxn, cSql )
 
/* 
Prepare the query 
*/ 
oSql:Create()
 
/* 
Test if query is allocated 
*/
if oSql:hQuery > 0
   /*
   Retrieve info about parameters to be passed to the Query
   Based on info below you can build an array of possible values to be passed as parameters 
   */
   nNumParam := oSql:GetNumParam()
   aTypeParam := oSql:GetParTypes()
 
   /* 
   Retrieve info about Columns returned by the Query 
   Based on the above info you can populate tables, arrays, etc 
   Possibilities are endless 
   */
   nNumCols := oSql:GetNumParam()
   aTypeCols := oSql:GetColTypes()
   aColNames := oSql:GetColNames()
 
   /* 
   Both methods of parameters passing are supported
   if oSql:Execute( aParams ) // Array - Unlimited number of parameters
   OR
   */
   if oSql:Execute( dDate, cCustId, cEmpty ) // Direct - Limited to 15 parameters
      /*
      It is time to retrieve the results
      */
      do while oSql:Skip()
         aadd( aResult, oSql:GetAllFields() )
      enddo
      /*
      There is another way to do the same
      This way you can retrieve only those fileds which interest you most
      But I do not see the logic to retrieve partial fields when we can
      optimize the query itself to include only needed fields.
      .
      do while oSql:Skip()
         aadd( aResult, { oSql:GetField( 1 ),oSql:GetField( 2 ),oSql:GetField( 3 ) } )
      enddo
      .
      OR
      .
      do while oSql:Skip()
         aadd( aResult, { oSql:GetField( aColNames[ 1 ] ),;
         oSql:GetField( aColNames[ 2 ] ),;
         oSql:GetField( aColNames[ 3 ] ) } )
      enddo
      .
      If you prefer to retrieve fields data by name.
      When retrieving data by name be careful to use column names off
      returned by the oSql:GetColNames() only as you never know about
      column names what SQL engine has assigned to them.
      */
      /*
      There is yet another way to retrieve records, viz.,
      Fetch fixed number of Records
      .
      aResult := oSql:FetchRecords( 10 )
      .
      OR For all remaining records
      .
      oSql:FetchRecords()
      The above two methods are provided for simplicity only.
      When these methods are called data rows are retrieved and stored in
      aRecords instance variable of the class and so later
      aResults := aclone( oSql:aRecords )
      */ 
 
   endif
endif
/*
Close the Query
Note that Query can still be EXECUTed with different parameters
DESTROYing a query will purge it permanently.
*/
oSql:Close()
/*
Must Destroy the Query to Free Resourses at the Server
Only if you need not to EXECUTE it again with different parameters
*/
oSql:Destroy()
Return aResult
//----------------------------------------------------------------------//