2.18 Open SQL and Microsoft SQL Server ODBC
The alternative Open SQL interface can be used with Microsoft SQL Server ODBC driver. This section provides some specific details and examples.
2.18.1 Placeholder Syntax
The placeholder syntax for Microsoft SQL Server is simply:
?
Here is an example:
INSERT INTO PICNICTABLE VALUES( ? )
Placeholders are not a portable feature. Different Access Modules use different placeholder syntax.
2.18.2 RWDBDataCallback and Open SQL
The data callback classes can be used with Microsoft SQL Server to insert and fetch data. These classes are useful for very large data that may not fit in memory for a single database call and are described in Section 15.3, “The Data Callback Classes,” in the DB Interface Module User’s Guide.
When fetching data, Microsoft SQL Server requires that the columns bound to a data callback must be at the end of the select list. For example, if two columns of type integer and LOB are selected, the integer must come before the LOB in the select list in order to use a data callback for the LOB column.
The DB Access Module for Microsoft SQL Server will fetch only one row on each call to RWDBOSql::fetch().
Multiple rows can be inserted in one call to RWDBOSql::execute(). The number of rows is determined by the entries parameter passed to the constructor of a data callback class or an RWDBTBuffer instance. Please see Chapter 15, “Using the Open SQL Classes,” in the DB Interface Module User’s Guide for information about array input.
2.18.2.1 Piece Size Setting
The DB Access Module for Microsoft SQL Server sets the piece size for inserting or fetching data to 32768. This value represents bytes in the case of binary data, and code units in the case of character data.
2.18.2.2 RWDBDataCallback::getLength()
When inserting data using data callbacks, some databases need to be provided the total length of the inserted value, which requires overriding the RWDBDataCallback::getLength() method.
*When using the SQL Server Native Client or ODBC Driver for SQL Server
The Microsoft SQL Server Native Client and ODBC Driver for SQL Server do not need the total length of the value, although there are potential performance benefits to using it. Therefore, overriding the RWDBDataCallback::getLength() method may be desirable but is not required.
*When using the Microsoft SQL Server MDAC Client
The MDAC Client requires the total length of the insert value to insert data successfully. Therefore, the method RWDBDataCallback::getLength() must be overridden when inserting data.
2.18.2.3 Stored Procedures and RWDBDataCallback
Stored procedure execution is not supported with the data callback classes.
2.18.3 Stored Procedures and Open SQL
Standard ODBC CALL syntax can be used when executing stored procedures with RWDBOSql.
 
RWDBOSql myOpenSqlObject("{ CALL myStoredProc(?, ?) }", RWDBOSql::Procedure);
In this example, myStoredProc is the name of the stored procedure, and ? characters are the placeholders for the parameters to be passed to the stored procedure. Note the provided hint RWDBOSql::Procedure.
 
RWDBOSql myOpenSqlObject("{ ? = CALL myStoredProc(?, ?)}", RWDBOSql::Procedure);
This example uses an additional placeholder to receive the return value of the stored procedure as an OUT parameter. A SQL Server stored procedure is allowed to have return value only of type integer.
Please see Section 2.18.5.3 for an example that uses RWDBOSql with stored procedures.
Stored procedure execution is not supported when using the data callback classes.
2.18.3.1 Sequence of Fetching Result Sets and Out Parameters
For Microsoft SQL Server stored procedures that return a result set, the SQL Server driver returns the OUT parameters only after the result sets are returned. Hence, fetchReturnParams() should be called only after completely processing all the result sets, otherwise the buffers bound to the OUT parameter will not have the correct output value.
2.18.4 Executing Cancel in Open SQL
Method RWDBOSql::cancel() can be used either to cancel the current result set or to cancel all pending result sets of the current execution. This method should be called only after the RWDBOSql object is executed.
Method RWDBOSql::cancel(RWDBOSql::Current) cancels the current result set and moves the RWDBOSql to the next available result set, if any.
Method RWDBOSql::cancel(RWDBOSql::All) cancels all the pending result sets. No subsequent results will be available from the current execution after this call has been made.
2.18.5 Open SQL Examples
These examples illustrate how an RWDBOSql object can be used to:
*Execute an INSERT statement, Section 2.18.5.1, “An Open SQL Insert Example.”
*Execute a SELECT query and retrieve its results, Section 2.18.5.2, “Open SQL Query Examples.”
*Execute a stored procedure and process its parameters and result sets, Section 2.18.5.3, “An Open SQL Stored Procedure Example.”
All statements are based on an employee table emp. The examples assume an error handler is associated with the connection in use, and don’t check for errors after calls to the RWDBOSql object.
The structure of the table emp is:
 
empno INT NOT NULL
ename VARCHAR(10) NOT NULL
posn VARCHAR(9) NOT NULL
mgr INT NULL
sal NUMERIC(7,2) NOT NULL
dept INT NOT NULL
2.18.5.1 An Open SQL Insert Example
This example shows how to execute an INSERT statement using RWDBOSql to insert data into the emp table.
 
const size_t NUM_OF_ROWS = 14; //1
 
RWDBTBuffer<int> empnoBuff(NUM_OF_ROWS), mgrBuff(NUM_OF_ROWS),
deptBuff(NUM_OF_ROWS);
RWDBTBuffer<RWCString> enameBuff(NUM_OF_ROWS), posnBuff(NUM_OF_ROWS);
RWDBTBuffer<RWDecimalPortable> salBuff(NUM_OF_ROWS); //2
 
empnoBuff[0] = 1;
enameBuff[0] = "ABC XYZ";
posnBuff[0] = "ADMIN";
mgrBuff[0] = 6;
salBuff[0] = "58000.00";
deptBuff[0] = 4; //3
 
// Populate rest of the rows
 
RWDBOSql openSql("INSERT INTO emp VALUES(?, ?, ?, ?, ?, ?)",
RWDBOSql::NonQuery); //4
 
openSql << empnoBuff << enameBuff << posnBuff
<< mgrBuff << salBuff << deptBuff; //5
 
openSql.execute(cn); //6
 
long rowsInserted = openSql.rowsAffected(); //7
 
std::cout << (openSql.isValid() ? "Data insertion successful." //8
: "Data insertion failed.") << std::endl;
if (rowsInserted >= 0) {
std::cout << "Inserted " << rowsInserted << " rows." << std::endl;
}
//1 Defines a constant for the number of rows to insert into the table for each insert operation. This is the array size of the bound variables.
//2 Defines buffers for holding the data to be inserted. A buffer is defined for every column in the table. The datatype on which each RWDBTBuffer object is templatized depends on the server type of each column in the table. Mappings between the C++ types and server datatypes are provided in Section 2.3, “Datatypes.” The number of entries in the buffer equals the number of rows to be inserted.
//3 Fills the buffers with data for the first row and subsequent rows using the index operator on the RWDBTBuffer objects. Alternatively, you could write a separate subroutine to fill the buffer with data.
//4 Creates an RWDBOSql object with the INSERT SQL statement using the statement type hint of RWDBOSql::NonQuery. The INSERT statement uses placeholders for each column in the table. Buffers need to be bound for the placeholders before the RWDBOSql is ready for execution.
//5 Binds the input buffers to the RWDBOSql in the same sequence as columns in the table.
//6 Executes the RWDBOSql on the supplied RWDBConnection.
//7 Fetches the number of rows inserted using method rowsAffected(). Note that some rows may be inserted even if the insert operation fails.
//8 Determines the success of the insert operation using the isValid() method on RWDBOSql.
2.18.5.2 Open SQL Query Examples
These examples illustrate how an RWDBOSql object can be used to execute SELECT queries and retrieve their results. The SELECT query is executed on table emp and retrieves all columns of the table for employees in a particular department.
The first example assumes awareness of the structure of table emp (Section 2.18.5) and hence the schema of the result set returned by the query.
 
RWDBOSql openSql("SELECT * FROM emp WHERE dept = ?", RWDBOSql::Query); //1
RWDBTBuffer<int> queryDeptBuff; //2
queryDeptBuff[0] = 3; //3
 
openSql << queryDeptBuff; //4
openSql.execute(cn); //5
 
const size_t ROWS_TO_FETCH = 5; //6
 
RWDBTBuffer<int> empnoBuff(ROWS_TO_FETCH), mgrBuff(ROWS_TO_FETCH),
deptBuff(ROWS_TO_FETCH);
RWDBTBuffer<RWCString> enameBuff(ROWS_TO_FETCH), posnBuff(ROWS_TO_FETCH);
RWDBTBuffer<RWDecimalPortable> salBuff(ROWS_TO_FETCH); //7
 
openSql[0] >> empnoBuff >> enameBuff >> posnBuff >>
mgrBuff >> salBuff >> deptBuff; //8
 
long rowsFetched = 0;
while ((rowsFetched = openSql.fetch().rowsFetched()) > 0) { //9
for (size_t i = 0; i < rowsFetched; ++i) {
cout << empnoBuff[i] << "\t" << enameBuff[i] << "\t"
<< posnBuff[i] << "\t"; //10
if (mgrBuff.isNull(i)) { //11
cout << "NULL";
}
else {
cout << mgrBuff[i];
}
cout << "\t" << salBuff[i] << "\t" << deptBuff[i] << endl;
}
}
//1 Creates an RWDBOSql object with the supplied SQL query and the hint RWDBOSql::Query to indicate that the SQL is a SELECT statement. The “?” in the SQL statement is a placeholder for the department number whose employees are to be selected. The placeholder needs to be bound before executing the RWDBOSql.
//2 Creates an RWDBTBuffer object to bind to the placeholder. This object is templatized on type int, as the dept column is of server type INT. The buffer will have a single entry of type int.
//3 Uses the index operator to access the single entry from the buffer created in //2, assigning it the department number for which records will be queried.
//4 Binds the buffer to the RWDBOSql object. The SQL query has only one placeholder, hence only one buffer needs to be bound. The RWDBOSql object is now ready for execution.
//5 Executes the RWDBOSql object on a supplied RWDBConnection object.
//6 Defines a constant for the number of rows to fetch at a time from the query result set.
//7 Creates RWDBTBuffer objects to be used in fetching the query result set, with one object per column of the result set. The objects are templatized on the expected server types of the result columns as mapped to SourcePro types. (See the type mapping in Section 2.3, “Datatypes.”) Each buffer is created with the number of entries equal to the number of rows to be fetched at a time.
//8 Binds the output buffers to the first RWDBMultiRow object in the RWDBOSql. As this is the first (and only) result set from the execution of the RWDBOSql, the buffers are bound to the first RWDBMultiRow object accessed using the index operator on RWDBOSql.
//9 Fetches the result into the bound buffers with each call to fetch() . The call rowsFetched() returns the number of rows fetched. The call to fetch() is repeated until all rows in the result set are fetched, indicated when rowsFetched() returns 0.
//10 Uses the index operator on the bound output buffers to access the fetched data.
//11 Uses the isNull() method on the bound output buffer mgrBuff to determine if a particular entry in the buffer is NULL.
The second example assumes execution of an ad hoc query for which the schema of the result set is not known beforehand.
 
RWDBOSql openSql("SELECT * FROM emp WHERE dept = ?", RWDBOSql::Query);
RWDBTBuffer<int> queryDeptBuff;
queryDeptBuff[0] = 3;
 
openSql << queryDeptBuff;
openSql.execute(cn);
 
const size_t ROWS_TO_FETCH = 5;
 
RWDBMultiRow mRow(openSql.schema(), ROWS_TO_FETCH); //1
openSql[0] = mRow; //2
 
long rowsFetched = 0;
while ((rowsFetched = openSql.fetch().rowsFetched()) > 0) { //3
for (size_t row = 0; row < rowsFetched; ++row) {
for (size_t col = 0; col < mRow.numberOfColumns(); ++col) { //4
RWDBValue val = mRow[row][col]; //5
if ( val.isNull() ) { //6
cout << "NULL" << "\t";
}
else {
cout << val.asString() << "\t";
}
}
cout << endl;
}
}
The steps through RWDBOSql execution are the same as in the previous example.
//1 Fetches the schema of the available result set by using the schema() method on the RWDBOSql. (You can use the RWDBSchema API to browse through the contents of the result set schema.) Here, we use the returned schema directly to create an RWDBMultiRow which creates buffers matching the datatypes of the result set columns. Each buffer will be created with ROWS_TO_FETCH entries.
//2 Assigns the created RWDBMultiRow to the RWDBOSql for use in fetching the first result set, by assigning to the 0th position in the RWDBOSql's result sets.
//3 Fetches rows into the buffers in the RWDBMultiRow with the fetch() call. The number of rows fetched is returned by the rowsFetched() call. This is repeated until all rows in the result set are fetched, indicated when rowsFetched() returns 0.
//4 Finds the number of buffers in the RWDBMultiRow using method numberOfColumns().
//5 Accesses each row’s data inside the RWDBMultiRow buffers as an RWDBRow object by calling the index operator on RWDBMultiRow and passing in the row number needed. Each value in the RWDBRow instance can then be accessed as an RWDBValue instance using the index operator on the RWDBRow and passing in the column number needed.
//6 The RWDBValue API can be used to determine the type of the value, to check if it is NULL, or to process it in other ways.
2.18.5.3 An Open SQL Stored Procedure Example
This example shows how to invoke a stored procedure, EmpInDept, using an RWDBOSql object, fetch result sets and fetch output parameters.
 
CREATE PROCEDURE EmpInDept(@deptno INT, @empcount INT OUT)
AS
SELECT @empcount = COUNT(*) FROM emp WHERE emp.dept = @deptno
SELECT empno, ename FROM emp WHERE emp.dept = @deptno
SELECT MIN(sal), AVG(sal), MAX(sal) FROM emp WHERE emp.dept = @deptno
The stored procedure takes a department number as an input parameter and provides the count of employees in that department as an output parameter. It produces two result sets: the first returns a list of employee number and name for each employee in that department; the second result set returns a single row with three columns containing the minimum, average and maximum salary of employees in that department.
 
RWDBOSql openSql ("{CALL EmpInDept(?, ?) }", RWDBOSql::Procedure); //1
 
RWDBTBuffer<int> dept, empCount; //2
empCount.paramType(RWDBColumn::outParameter); //3
 
openSql << dept << empCount; //4
 
dept[0] = 2; //5
openSql.execute(cn); //6
 
const size_t NUMBER_OF_ROWS = 10;
RWDBTBuffer<int> empNums (NUMBER_OF_ROWS); //7
RWDBTBuffer<RWCString> empNames (NUMBER_OF_ROWS); //8
 
openSql[0] >> empNums >> empNames; //9
 
long count = 0;
while ((count = openSql.fetch().rowsFetched()) > 0 ) { //10
for (int row = 0; row < count; row++) {
std::cout << "Employee: " << empNums[row] << ", "
<< empNames[row] << std::endl; //11
}
}
 
RWDBTBuffer<RWDecimalPortable> minBuff, avgBuff, maxBuff; //12
 
openSql[1] >> minBuff >> avgBuff >> maxBuff; //13
 
openSql.fetch(); //14
std::cout << "\nMinimum Salary: " << minBuff[0]
<< "\nAverage Salary: " << avgBuff[0]
<< "\nMaximum Salary: " << maxBuff[0]
<< std::endl; //15
 
openSql.fetchReturnParams(); //16
std::cout << "Number of Employees in dept " << dept[0] << ": "
<< empCount[0] << std::endl; //17
//1 Creates an RWDBOSql object and passes in the CALL statement to invoke the stored procedure EmpInDept. Two placeholders are used to indicate the in and out parameters to be bound before the stored procedure can be executed. Note the hint provided as RWDBOSql::Procedure.
//2 Creates parameter buffers, dept and empCount, both of type int. Both buffers contain a single entry.
//3 Sets the parameter type of empCount buffer as out parameter, as it will be bound to the stored procedure OUT parameter.
//4 Binds the buffers to the openSql object in the same sequence as the parameters are defined in the stored procedure.
//5 Sets the department number to be queried in the single entry in the dept buffer.
//6 Executes the openSql object, which invokes the stored procedure using the department number defined in the dept buffer.
//7-8 Defines output buffers empNums and empNames for the first result set. They are defined of type int and RWCString to match the datatypes of employee number and employee name. Each buffer has 10 entries enabling at most 10 rows to be fetched in a single fetch() call.
//9 Uses the RWDBOSql index operator to bind the output buffers to the first result set of the openSql object, indicated by index 0. The buffers are bound in the same sequence as columns in the result set.
//10 Uses a while loop to fetch rows from the first result set in the empNums and empNames buffers until all rows are fetched, indicated when fetch() returns 0 rows. The next call to fetch() will fetch the second result set.
//11 Accesses data using the index operator on the buffers empNums and empNames.
//12 Defines three buffers for the second result set, one for each column in the set — minimum, average, and maximum salary. Each buffer is created with a single entry, as a single row is expected.
//13 Binds the buffers, in sequence, to the second result set of the openSql object, indicated by index of 1.
//14 Fetches the values in the bound buffers. Because a single row is expected from the fetch, no while loop is used.
//15 Accesses data using the index operator on the buffers. Because they are single entry buffers, the only index used is 0.
//16 Fetches the OUT parameters of the stored procedure. The OUT parameter buffer, empCount, now has the output value produced by the stored procedure. Note that this call is made after completely processing the returned result sets. Please see Section 2.18.3.1, “Sequence of Fetching Result Sets and Out Parameters,” for more information on fetching output parameters after processing result sets.
//17 Accesses the value using the index operator on the buffer empCount.