2 Working with TimesTen Databases in ODBC

This chapter covers TimesTen programming features and describes how to use ODBC to connect to and use the TimesTen database. It includes the following topics:

Notes:

TimesTen supports:

Managing TimesTen database connections

For TimesTen Scaleout, refer to Oracle TimesTen In-Memory Database Scaleout User's Guide for information about creating a database and connecting to a database, using either a direct connection or a client/server connection. See "Creating a database" and "Connecting to a database".

For TimesTen Classic, Oracle TimesTen In-Memory Database Operations Guide contains information about creating a DSN for the database. The type of DSN you create depends on whether your application connects directly to the database or connects through a client:

  • If you intend to connect directly to the database, refer to "Managing TimesTen Databases" in Oracle TimesTen In-Memory Database Operations Guide. There are sections on creating a DSN for a direct connection from Linux or UNIX or from Windows.

  • If you intend to create a client connection to the database, refer to "Working with the TimesTen Client and Server" in Oracle TimesTen In-Memory Database Operations Guide. There are sections on creating a DSN for a client/server connection from Linux or UNIX or from Windows.

Notes:

  • ODBC applications can connect to a database by referencing either its attributes (host, port number, and so on) or its data source name (DSN). In TimesTen Classic, users can create DSNs directly. In TimesTen Scaleout, a DSN is created for each connectable you define in the grid.

  • In TimesTen, the user name and password must be for a valid user who has been granted CREATE SESSION privilege to connect to the database.

  • A TimesTen connection cannot be inherited from a parent process. If a process opens a database connection before creating (forking) a child process, the child must not use the connection.

The rest of this section covers the following topics:

SQLConnect, SQLDriverConnect, SQLAllocConnect, SQLDisconnect functions

The following ODBC functions are available for connecting to a database and related functionality:

  • SQLConnect: Loads a driver and connects to the database. The connection handle points to where information about the connection is stored, including status, transaction state, results, and error information.

  • SQLDriverConnect: This is an alternative to SQLConnect when more information is required than what is supported by SQLConnect, which is just data source (the database), user name, and password.

  • SQLAllocConnect: Allocates memory for a connection handle within the specified environment.

  • SQLDisconnect: Disconnect from the database. Takes the existing connection handle as its only argument.

Refer to ODBC API reference documentation for additional details about these functions.

Connecting to and disconnecting from a database

This section provides examples of connecting to and disconnecting from the database.

Example 2-1 Connect and disconnect (excerpt)

This code fragment invokes SQLConnect and SQLDisconnect to connect to and disconnect from the database named FixedDs. The first invocation of SQLConnect by any application causes the creation of the FixedDs database. Subsequent invocations of SQLConnect would connect to the existing database.

#include <timesten.h>
SQLRETURN retcode;
SQLHDBC hdbc;

...
retcode = SQLConnect(hdbc,
                     (SQLCHAR*)"FixedDs", SQL_NTS,
                     (SQLCHAR*)"johndoe", SQL_NTS,
                     (SQLCHAR*)"opensesame", SQL_NTS);
...
retcode = SQLDisconnect(hdbc);
...

Example 2-2 Connect and disconnect (complete program)

This example contains a complete program that creates, connects to, and disconnects from a database. The example uses SQLDriverConnect instead of SQLConnect to set up the connection, and uses SQLAllocConnect to allocate memory. It also shows how to get error messages. (In addition, you can refer to "Handling Errors".)

#include <timesten.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static void chkReturnCode(SQLRETURN rc, SQLHENV henv,
                          SQLHDBC hdbc, SQLHSTMT hstmt,
                          char* msg, char* filename,
                          int lineno, BOOL err_is_fatal);
#define DEFAULT_CONNSTR "DSN=sampledb;PermSize=32"

int
main(int ac, char** av)
{
   SQLRETURN rc = SQL_SUCCESS;
                  /* General return code for the API */
   SQLHENV henv = SQL_NULL_HENV;
                  /* Environment handle */
   SQLHDBC hdbc = SQL_NULL_HDBC;
                  /* Connection handle */
   SQLHSTMT hstmt = SQL_NULL_HSTMT;
                  /* Statement handle */
   SQLCHAR connOut[255];
                  /* Buffer for completed connection string */
   SQLSMALLINT connOutLen;
                  /* Number of bytes returned in ConnOut */
   SQLCHAR *connStr = (SQLCHAR*)DEFAULT_CONNSTR;
                  /* Connection string */
   rc = SQLAllocEnv(&henv);
   if (rc != SQL_SUCCESS) {
      fprintf(stderr, "Unable to allocate an "
             "environment handle\n");
    exit(1);
   }
   rc = SQLAllocConnect(henv, &hdbc);
   chkReturnCode(rc, henv, SQL_NULL_HDBC,
              SQL_NULL_HSTMT,
              "Unable to allocate a "
              "connection handle\n",
              __FILE__, __LINE__, 1);

   rc = SQLDriverConnect(hdbc, NULL,
                         connStr, SQL_NTS,
                         connOut, sizeof(connOut),
                         &connOutLen,
                         SQL_DRIVER_NOPROMPT);
   chkReturnCode(rc, henv, hdbc, SQL_NULL_HSTMT,
                 "Error in connecting to the"
                 " database\n",
                 __FILE__, __LINE__, 1);
   rc = SQLAllocStmt(hdbc, &hstmt);
   chkReturnCode(rc, henv, hdbc, SQL_NULL_HSTMT,
                 "Unable to allocate a "
                 "statement handle\n",
                 __FILE__, __LINE__, 1);

   /* Your application code here */

   if (hstmt != SQL_NULL_HSTMT) {
     rc = SQLFreeStmt(hstmt, SQL_DROP);
     chkReturnCode(rc, henv, hdbc, hstmt,
                   "Unable to free the "
                   "statement handle\n",
                    __FILE__, __LINE__, 0);
   }

   rc = SQLDisconnect(hdbc);
   chkReturnCode(rc, henv, hdbc,
                 SQL_NULL_HSTMT,
                 "Unable to close the "
                 "connection\n",
                 __FILE__, __LINE__, 0);

   rc = SQLFreeConnect(hdbc);
   chkReturnCode(rc, henv, hdbc,
                 SQL_NULL_HSTMT,
                 "Unable to free the "
                 "connection handle\n",
                 __FILE__, __LINE__, 0);

   rc = SQLFreeEnv(henv);
   chkReturnCode(rc, henv, SQL_NULL_HDBC,
                 SQL_NULL_HSTMT,
                 "Unable to free the "
                 "environment handle\n",
                 __FILE__, __LINE__, 0);
     return 0;
   }
}

static void
chkReturnCode(SQLRETURN rc, SQLHENV henv,
              SQLHDBC hdbc, SQLHSTMT hstmt,
              char* msg, char* filename,
              int lineno, BOOL err_is_fatal)
{
   #define MSG_LNG 512
   SQLCHAR sqlState[MSG_LNG];
   /* SQL state string */
   SQLINTEGER nativeErr;
   /* Native error code */
   SQLCHAR errMsg[MSG_LNG];
   /* Error msg text buffer pointer */
   SQLSMALLINT errMsgLen;
   /* Error msg text Available bytes */
   SQLRETURN ret = SQL_SUCCESS;
   if (rc != SQL_SUCCESS &&
       rc != SQL_NO_DATA_FOUND ) {
      if (rc != SQL_SUCCESS_WITH_INFO) {
       /*
        * It's not just a warning
        */
      fprintf(stderr, "*** ERROR in %s, line %d:"
               " %s\n",
               filename, lineno, msg);
  }
  /*
   * Now see why the error/warning occurred
   */
  while (ret == SQL_SUCCESS ||
         ret == SQL_SUCCESS_WITH_INFO) {
    ret = SQLError(henv, hdbc, hstmt,
                   sqlState, &nativeErr,
                   errMsg, MSG_LNG,
                   &errMsgLen);
    switch (ret) {
      case SQL_SUCCESS:
        fprintf(stderr, "*** %s\n"
                 "*** ODBC Error/Warning = %s, "
                 "TimesTen Error/Warning "
                 " = %d\n",
                 errMsg, sqlState,
                 nativeErr);
      break;
    case SQL_SUCCESS_WITH_INFO:
      fprintf(stderr, "*** Call to SQLError"
              " failed with return code of "
              "SQL_SUCCESS_WITH_INFO.\n "
              "*** Need to increase size of"
              " message buffer.\n");
      break;
    case SQL_INVALID_HANDLE:
      fprintf(stderr, "*** Call to SQLError"
              " failed with return code of "
              "SQL_INVALID_HANDLE.\n");
      break;
    case SQL_ERROR:
      fprintf(stderr, "*** Call to SQLError"
              " failed with return code of "
              "SQL_ERROR.\n");
      break;
    case SQL_NO_DATA_FOUND:
      break;
     } /* switch */
   } /* while */
   if (rc != SQL_SUCCESS_WITH_INFO && err_is_fatal) {
     fprintf(stderr, "Exiting.\n");
     exit(-1);
   }
}

Setting connection attributes programmatically

You can set or override connection attributes programmatically by specifying a connection string when you connect to a database.

Refer to "Managing TimesTen Databases" in Oracle TimesTen In-Memory Database Operations Guide for general information about connection attributes. General connection attributes require no special privilege. First connection attributes are set when the database is first loaded, and persist for all connections. Only the instance administrator can load a database with changes to first connection attribute settings. Refer to "Connection Attributes" in Oracle TimesTen In-Memory Database Reference for additional information, including specific information about any particular connection attribute.

Example 2-3 Connect and use store-level locking

This code fragment connects to a database named mydsn and indicates in the SQLDriverConnect call that the application should use a passthrough setting of 3. Note that PassThrough is a general connection attribute.

SQLHDBC hdbc;
SQLCHAR ConnStrOut[512];
SQLSMALLINT cbConnStrOut;
SQLRETURN rc;

rc = SQLDriverConnect(hdbc, NULL,
    "DSN=mydsn;PassThrough=3", SQL_NTS,
    ConnStrOut, sizeof (ConnStrOut),
    &cbConnStrOut, SQL_DRIVER_NOPROMPT);

Note:

Each direct connection to a database opens several files. An application with many threads, each with a separate connection, has several files open for each thread. Such an application can exceed the maximum allowed (or configured maximum) number of file descriptors that may be simultaneously open on the operating system. In this case, configure your system to allow a larger number of open files. See "Limits on number of open files" in Oracle TimesTen In-Memory Database Reference.

Using a default DSN

In TimesTen Classic, a default DSN, simply named default, can be defined in the odbc.ini or sys.odbc.ini file. See "Setting up a default DSN in TimesTen Classic" in Oracle TimesTen In-Memory Database Operations Guide for information about defining a default DSN.

The associated data source would be connected to in the following circumstances when SQLConnect or SQLDriverConnect is called.

For SQLConnect, if a default DSN has been defined, it is used if ServerName specifies a data source that cannot be found, is a null pointer, or is specifically set to a value of default. For reference, here is the SQLConnect calling sequence:

SQLRETURN SQLConnect( 
          SQLHDBC        ConnectionHandle, 
          SQLCHAR *      ServerName, 
          SQLSMALLINT    NameLength1, 
          SQLCHAR *      UserName, 
          SQLSMALLINT    NameLength2, 
          SQLCHAR *      Authentication, 
          SQLSMALLINT    NameLength3); 

Use default as the server name. The user name and authentication values are used as is.

For SQLDriverConnect, if a default DSN has been defined, it is used if the connection string does not include the DSN keyword or if the data source cannot be found. For reference, here is the SQLDriverConnect calling sequence:

SQLRETURN SQLDriverConnect( 
          SQLHDBC         ConnectionHandle, 
          SQLHWND         WindowHandle, 
          SQLCHAR *       InConnectionString, 
          SQLSMALLINT     StringLength1, 
          SQLCHAR *       OutConnectionString, 
          SQLSMALLINT     BufferLength, 
          SQLSMALLINT *   StringLength2Ptr, 
          SQLUSMALLINT    DriverCompletion); 

Use default as the DSN keyword. The user name and password are used as is.

Be aware of the following usage notes when in direct mode versus client/server mode with a driver manager:

  • When you are not using a driver manager, TimesTen manages this functionality. The default DSN must be a TimesTen database.

  • When you are using a driver manager, the driver manager manages this functionality. The default DSN need not be a TimesTen database.

Managing TimesTen data

This section provides detailed information on working with data in a TimesTen database. It includes the following topics.

TimesTen include files

To use TimesTen features, include the TimesTen files shown in the following table, as applicable. They are located in the include directory of the TimesTen installation.

Include file Description
timesten.h TimesTen ODBC features

This file includes the appropriate version of sql.h: the TimesTen version on Linux or UNIX systems or the system version on Windows systems.

This file also includes sqltypes.h, sqlext.h, and sqlucode.h. On Windows systems, it also includes windows.h.

tt_errCode.h TimesTen error codes (optional—see notes)

This file maps TimesTen error codes to defined constants.


Set the include path appropriately to access any files that are to be included. See "Compiling and linking applications" for related information.

Notes:

  • If you include sql.h directly (instead of through timesten.h), on Windows you must include the system version of sql.h, not the TimesTen version.

  • Type definitions previously in sqlunix.h are now in sqltypes.h; however, sqlunix.h still exists (as an empty file) for backward compatibility.

  • There are alternatives to including tt_errCode.h. One is to move any desired constant definitions to timesten.h. Another is to reference the corresponding integer values directly in your code.

SQL statement execution within C applications

"Working with Data in a TimesTen Database" in Oracle TimesTen In-Memory Database Operations Guide describes how to use SQL to manage data. This section describes general formats used to execute a SQL statement within a C application. The following topics are covered:

SQLExecDirect and SQLExecute functions

There are two ODBC functions to execute SQL statements:

  • SQLExecute: Executes a statement that has been prepared with SQLPrepare. After the application is done with the results, they can be discarded and SQLExecute can be run again using different parameter values.

    This is typically used for DML statements with bind parameters, or statements that are being executed more than once.

  • SQLExecDirect: Prepares and executes a statement.

    This is typically used for DDL statements or for DML statements that would execute only a few times and without bind parameters.

Refer to ODBC API reference documentation for details about these functions.

Executing a SQL statement

You can use the SQLExecDirect function as shown in Example 2-4.

The next section, "Preparing and executing queries and working with cursors", shows usage of the SQLExecute and SQLPrepare functions.

Example 2-4 Executing a SQL statement with SQLExecDirect

This code sample creates a table, NameID, with two columns: CustID and CustName. The table maps character names to integer identifiers.

#include <timesten.h>
SQLRETURN rc;
SQLHSTMT hstmt;
...
rc = SQLExecDirect(hstmt, (SQLCHAR*)
     "CREATE TABLE NameID (CustID INTEGER, CustName VARCHAR(50))",
     SQL_NTS);
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
     ... /* handle error */

Preparing and executing queries and working with cursors

This section shows the basic steps of preparing and executing a query and working with cursors. Applications use cursors to scroll through the results of a query, examining one result row at a time.

Important:

In TimesTen, any operation that ends your transaction, such as a commit or rollback, closes all cursors associated with the connection.

In the ODBC setting, a cursor is always associated with a result set. This association is made by the ODBC driver. The application can control cursor characteristics, such as the number of rows to fetch at one time, using SQLSetStmtOption options documented in "Option support for ODBC 2.5 SQLSetStmtOption and SQLGetStmtOption". The steps involved in executing a query typically include the following.

  1. Use SQLPrepare to prepare the SELECT statement for execution.

  2. Use SQLBindParameter, if the statement has parameters, to bind each parameter to an application address. See "SQLBindParameter function". (Note that Example 2-5 below does not bind parameters.)

  3. Call SQLBindCol to assign the storage and data type for a column of results, binding column results to local variable storage in your application.

  4. Call SQLExecute to execute the SELECT statement. See "SQLExecDirect and SQLExecute functions".

  5. Call SQLFetch to fetch the results. Specify the statement handle.

  6. Call SQLFreeStmt to free the statement handle. Specify the statement handle and either SQL_CLOSE, SQL_DROP, SQL_UNBIND, or SQL_RESET_PARAMS.

Refer to ODBC API reference documentation for details on these ODBC functions. Examples are shown throughout this chapter and in the TimesTen sample applications. See "TimesTen Quick Start and sample applications".

Note:

By default (when connection attribute PrivateCommands=0), TimesTen shares prepared statements between connections, so subsequent prepares of the same statement on different connections execute very quickly.

Example 2-5 Executing a query and working with the cursor

This example illustrates how to prepare and execute a query using ODBC calls. Error checking has been omitted to simplify the example. In addition to ODBC functions mentioned previously, this example uses SQLNumResultCols to return the number of columns in the result set, SQLDescribeCol to return a description of one column of the result set (column name, type, precision, scale, and nullability), and SQLBindCol to assign the storage and data type for a column in the result set. These are all described in detail in ODBC API reference documentation.

#include <timesten.h>

SQLHSTMT hstmt;
SQLRETURN rc;
int i;
SQLSMALLINT numCols;
SQLCHAR colname[32];
SQLSMALLINT colnamelen, coltype, scale, nullable;
SQLULEN collen [MAXCOLS];
SQLLEN outlen [MAXCOLS];
SQLCHAR* data [MAXCOLS];

/* other declarations and program set-up here */

/* Prepare the SELECT statement */
rc = SQLPrepare(hstmt,
(SQLCHAR*) "SELECT * FROM EMP WHERE AGE>20",
SQL_NTS);
/* ... */

/* Determine number of columns in result rows */
rc = SQLNumResultCols(hstmt, &numCols);

/* ... */

/* Describe and bind the columns */
for (i = 0; i < numCols; i++) {
    rc = SQLDescribeCol(hstmt,
         (SQLSMALLINT) (i + 1),
         colname,(SQLSMALLINT)sizeof(colname), &colnamelen, &coltype, &collen[i],
         &scale, &nullable);

    /* ... */

   data[i] = (SQLCHAR*) malloc (collen[i] +1);  //Allocate space for column data.
   rc = SQLBindCol(hstmt, (SQLSMALLINT) (i + 1),
                   SQL_C_CHAR, data[i],
                   COL_LEN_MAX, &outlen[i]);

   /* ... */

}
/* Execute the SELECT statement */
rc = SQLExecute(hstmt);

/* ... */

/* Fetch the rows */
if (numCols > 0) {
  while ((rc = SQLFetch(hstmt)) == SQL_SUCCESS ||
          rc == SQL_SUCCESS_WITH_INFO) {
    /* ... "Process" the result row */
  } /* end of for-loop */
  if (rc != SQL_NO_DATA_FOUND)
    fprintf(stderr,
            "Unable to fetch the next row\n");

/* Close the cursor associated with the SELECT statement */
  rc = SQLFreeStmt(hstmt, SQL_CLOSE);
}

TimesTen deferred prepare

In standard ODBC, a SQLPrepare call compiles a SQL statement so that information about the statement, such as column descriptions for the result set, is available to the application and accessible through calls such as SQLDescribeCol. To accomplish this, the SQLPrepare call must communicate with the server for processing.

This is in contrast, for example, to expected behavior under Oracle Call Interface (OCI), where a prepare call is expected to be a lightweight operation performed on the client to simply extract names and positions of parameters.

To avoid unwanted round trips between client and server, and also to make the behavior consistent with OCI expectations, the TimesTen client library implementation of SQLPrepare performs what is referred to as a "deferred prepare", where the request is not sent to the server until required. Examples of when the round trip would be required:

  • When there is a SQLExecute call. Note that if there is a deferred prepare call that has not yet been sent to the server, a SQLExecute call on the client is converted to a SQLExecDirect call.

  • When there is a request for information about the query that can only be supplied by the SQL engine, such as when there is a SQLDescribeCol call, for example. Many such calls in standard ODBC can access information previously returned by a SQLPrepare call, but with the deferred prepare functionality the SQLPrepare call is sent to the server and the information is returned to the application only as needed.

Note:

Deferred prepare functionality is not implemented (and not necessary) with the TimesTen direct driver.

The deferred prepare implementation requires no changes at the application or user level; however, be aware that calling any of the following functions may result in a round trip to the server if the required information from a previously prepared statement has not yet been retrieved:

  • SQLColAttributes

  • SQLDescribeCol

  • SQLDescribeParam

  • SQLNumResultCols

  • SQLNumParams

  • SQLGetStmtOption (for options that depend on the statement having been compiled by the SQL engine)

Also be aware that when calling any of these functions, any error from an earlier SQLPrepare call may be deferred until one of these calls is executed. In addition, these calls may return errors specific to SQLPrepare as well as errors specific to themselves.

Prefetching multiple rows of data

A TimesTen extension to ODBC enables applications to prefetch multiple rows of data into the ODBC driver buffer. This can improve performance of client/server applications.

The TT_PREFETCH_COUNT ODBC statement option determines how many rows a SQLFetch call prefetches. Note that this option provides no benefit for an application using a direct connection to TimesTen.

You can set TT_PREFETCH_COUNT in a call to either SQLSetStmtOption or SQLSetConnectOption (which sets the option default value for all statements associated with the connection). The value can be any integer from 0 to 128, inclusive. Following is an example.

rc = SQLSetConnectOption(hdbc, TT_PREFETCH_COUNT, 100);

With this setting, the first SQLFetch call on the connection prefetches 100 rows. Subsequent SQLFetch calls fetch from the ODBC buffer instead of from the database, until the buffer is depleted. After it is depleted, the next SQLFetch call fetches another 100 rows into the buffer, and so on.

To disable prefetch, set TT_PREFETCH_COUNT to 1.

When you set the prefetch count to 0, TimesTen uses a default prefetch count according to the isolation level you have set for the database, and sets TT_PREFETCH_COUNT to that value. With Read Committed isolation level, the default prefetch value is 5. With Serializable isolation level, the default is 128. The default prefetch value is a good setting for most applications. Generally, a higher value may result in better performance for larger result sets, at the expense of slightly higher resource use.

You can also see "Option support for ODBC 2.5 SQLSetStmtOption and SQLGetStmtOption" for information about statement options, including TT_PREFETCH_COUNT and SQL_TXN_ISOLATION.

Optimizing query performance

A TimesTen extension to ODBC enables applications to optimize read-only query performance in client/server applications by using the TT_PREFETCH_CLOSE ODBC connection option. Set TT_PREFETCH_CLOSE to TT_PREFETCH_CLOSE_ON using SQLSetConnectOption.

All transactions should be committed when executed, including read-only transactions. When TT_PREFETCH_CLOSE is set to TT_PREFETCH_CLOSE_ON, the server automatically closes the cursor and commits the transaction after the server has prefetched all rows of the result set for a read-only query. This enhances performance by reducing the number of network round-trips between client and server.

The client should still free the statement with SQLFreeStmt(SQL_CLOSE) and commit the transaction with SQLTransact(SQL_COMMIT), but those calls are executed in the client and do not require a network round trip between the client and server.

Notes:

  • Do not use multiple statement handles for the same connection when TT_PREFETCH_CLOSE is set to TT_PREFETCH_CLOSE_ON. The server may fetch all of the result set, commit the transaction, and close the statement handle before the client is finished, resulting in the closing of all statement handles.

  • This option is ignored for TimesTen direct connections and for SELECT FOR UPDATE statements.

The following example shows how to use the TT_PREFETCH_CLOSE option.

SQLSetConnectOption (hdbc, TT_PREFETCH_CLOSE, TT_PREFETCH_CLOSE_ON);
SQLExecDirect (hstmt, "SELECT * FROM T", SQL_NTS);
while (SQLFetch (hstmt) != SQL_NO_DATA_FOUND)
{
// do the processing and error checking
}
SQLFreeStmt (hstmt, SQL_CLOSE);
SQLTransact(SQL_COMMIT);

Binding parameters and executing statements

This section discusses how to bind input or output parameters for SQL statements. The following topics are covered.

Note:

The term "bind parameter" as used in TimesTen developer guides (in keeping with ODBC terminology) is equivalent to the term "bind variable" as used in TimesTen PL/SQL documents (in keeping with Oracle Database PL/SQL terminology).

SQLBindParameter function

The ODBC SQLBindParameter function is used to bind parameters for SQL statements. This could include input, output, or input/output parameters.

To bind an input parameter through ODBC, use the SQLBindParameter function with a setting of SQL_PARAM_INPUT for the fParamType argument. Refer to ODBC API reference documentation for details about the SQLBindParameter function. Table 2-1 provides a brief summary of its arguments.

To bind an output or input/output parameter through ODBC, use the SQLBindParameter function with a setting of SQL_PARAM_OUTPUT or SQL_PARAM_INPUT_OUTPUT, respectively, for the fParamType argument. As with input parameters, use the fSqlType, cbColDef, and ibScale arguments (as applicable) to specify data types.

Table 2-1 SQLBindParameter arguments

Argument Type Description

hstmt

SQLHSTMT

Statement handle

ipar

SQLUSMALLINT

Parameter number, sequentially from left to right, starting with 1

fParamType

SQLSMALLINT

Indicating input or output: SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, or SQL_PARAM_INPUT_OUTPUT

fCType

SQLSMALLINT

C data type of the parameter

fSqlType

SQLSMALLINT

SQL data type of the parameter

cbColDef

SQLULEN

The precision of the parameter, such as the maximum number of bytes for binary data, the maximum number of digits for a number, or the maximum number of characters for character data

ibScale

SQLSMALLINT

The scale of the parameter, referring to the maximum number of digits to the right of the decimal point, where applicable

rgbValue

SQLPOINTER

Pointer to a buffer for the data of the parameter

cbValueMax

SQLLEN

Maximum length of the rgbValue buffer, in bytes

pcbValue

SQLLEN*

Pointer to a buffer for the length of the parameter


Note:

Refer to "Data Types" in Oracle TimesTen In-Memory Database SQL Reference for information about precision and scale of TimesTen data types.

Determination of parameter type assignments and type conversions

Bind parameter type assignments are determined as follows.

  • Parameter type assignments for statements that execute in TimesTen are determined by TimesTen. Specifically:

    • For SQL statements that execute within TimesTen, the TimesTen query optimizer determines data types of SQL parameters.

  • Parameter type assignments for statements that execute in Oracle Database, or according to Oracle Database functionality, are determined by the application as follows.

    • For SQL statements that execute within Oracle Database—that is, passthrough statements from the TimesTen Application-Tier Database Cache (TimesTen Cache)—the application must specify data types through its calls to the ODBC SQLBindParameter function, according to the fSqlType, cbColDef, and ibScale arguments of that function, as applicable.

    • For PL/SQL blocks or procedures that execute within TimesTen, where the PL/SQL execution engine has the same basic functionality as in Oracle Database, the application must specify data types through its calls to SQLBindParameter (the same as for SQL statements that execute within Oracle Database).

      So regarding host binds for PL/SQL (the variables, or parameters, that are preceded by a colon within a PL/SQL block), note that the type of a host bind is effectively declared by the call to SQLBindParameter, according to fSqlType and the other arguments as applicable, and is not declared within the PL/SQL block.

The ODBC driver performs any necessary type conversions between C values and SQL or PL/SQL types. For any C-to-SQL or C-to-PL/SQL combination that is not supported, an error occurs. These conversions can be from a C type to a SQL or PL/SQL type (input parameter), from a SQL or PL/SQL type to a C type (output parameter), or both (input/output parameter).

Note:

The TimesTen binding mechanism (early binding) differs from that of Oracle Database (late binding). TimesTen requires the data types before preparing queries. As a result, there will be an error if the data type of each bind parameter is not specified or cannot be inferred from the SQL statement. This would apply, for example, to the following statement:
SELECT 'x' FROM DUAL WHERE ? = ?;

You could address the issue as follows, for example:

SELECT 'x' from DUAL WHERE CAST(? as VARCHAR2(10)) = 
                           CAST(? as VARCHAR2(10)); 

Table 2-2 documents the mapping between ODBC types and SQL or PL/SQL types.

Table 2-2 ODBC SQL to TimesTen SQL or PL/SQL type mappings

ODBC type (fSqlType) SQL or PL/SQL type TimesTen support notes

SQL_BIGINT

NUMBER

No notes

SQL_BINARY

RAW(p)

No notes

SQL_BIT

PLS_INTEGER

No notes

SQL_CHAR

CHAR(p)

No notes

SQL_DATE

DATE

No notes

SQL_DECIMAL

NUMBER

No notes

SQL_DOUBLE

NUMBER

No notes

SQL_FLOAT

BINARY_DOUBLE

No notes

SQL_INTEGER

PLS_INTEGER

No notes

SQL_INTERVAL_DAY

N/A

See notes after this table.

SQL_INTERVAL_DAY_TO_HOUR

N/A

See notes after this table.

SQL_INTERVAL_DAY_TO_MINUTE

N/A

See notes after this table.

SQL_INTERVAL_DAY_TO_SECOND

N/A

See notes after this table.

SQL_INTERVAL_HOUR

N/A

See notes after this table.

SQL_INTERVAL_HOUR_TO_MINUTE

N/A

See notes after this table.

SQL_INTERVAL_HOUR_TO_SECOND

N/A

See notes after this table.

SQL_INTERVAL_MINUTE

N/A

See notes after this table.

SQL_INTERVAL_MINUTE_TO_SECOND

N/A

See notes after this table.

SQL_INTERVAL_MONTH

N/A

See notes after this table.

SQL_INTERVAL_YEAR

N/A

See notes after this table.

SQL_INTERVAL_YEAR_TO_MONTH

N/A

See notes after this table.

SQL_INTERVAL_SECOND

N/A

See notes after this table.

SQL_NUMERIC

NUMBER

No notes

SQL_REAL

BINARY_FLOAT

No notes

SQL_REFCURSOR

REF CURSOR

No notes

SQL_ROWID

ROWID

No notes

SQL_SMALLINT

PLS_INTEGER

No notes

SQL_TIME

TIME

TimesTen does not support TIMEZONE. TIME data type values are stored without making any adjustment for time difference. Applications must assume one time zone and convert TIME values to that time zone before sending the values to the database.

SQL_TIMESTAMP

TIMESTAMP(s)

Same consideration as for TIME.

SQL_TINYINT

PLS_INTEGER

No notes

SQL_VARBINARY

RAW(p)

No notes

SQL_VARCHAR

VARCHAR2(p)

No notes

SQL_WCHAR

NCHAR(p)

No notes

SQL_WVARCHAR

NVARCHAR2(p)

No notes


Notes:

  • The notation (p) indicates precision is according to the SQLBindParameter argument cbColDef.

  • The notation (s) indicates scale is according to the SQLBindParameter argument ibScale.

  • The SQL_INTERVAL_xxxx types are supported only for computing values, such as in SQL expressions, not as database column types.

  • Most applications should use SQL_VARCHAR rather than SQL_CHAR for binding character data. Use of SQL_CHAR may result in unwanted space padding to the full precision of the parameter type.

  • Regarding TIME and TIMESTAMP, for example, an application can assume its time zone to be Pacific Standard Time. If the application is using TIME and TIMESTAMP values from Pacific Daylight Time or Eastern Standard Time, for example, the application must convert TIME and TIMESTAMP to Pacific Standard Time.

Binding input parameters

For input parameters to PL/SQL in TimesTen, use the fSqlType, cbColDef, and ibScale arguments (as applicable) of the ODBC SQLBindParameter function to specify data types. This is in contrast to how SQL input parameters are supported, as noted in "Determination of parameter type assignments and type conversions".

In addition, the rgbValue, cbValueMax, and pcbValue arguments of SQLBindParameter are used as follows for input parameters:

  • rgbValue: Before statement execution, points to the buffer where the application places the parameter value to be passed to the application.

  • cbValueMax: For character and binary data, indicates the maximum length of the incoming value that rgbValue points to, in bytes. For all other data types, cbValueMax is ignored, and the length of the value that rgbValue points to is determined by the length of the C data type specified in the fCType argument of SQLBindParameter.

  • pcbValue: Points to a buffer that contains one of the following before statement execution:

    • The actual length of the value that rgbValue points to

      Note: For input parameters, this would be valid only for character or binary data.

    • SQL_NTS for a null-terminated string

    • SQL_NULL_DATA for a null value

Binding output parameters

For output parameters from PL/SQL in TimesTen, as noted for input parameters previously, use the fSqlType, cbColDef, and ibScale arguments (as applicable) of the ODBC SQLBindParameter function to specify data types.

In addition, the rgbValue, cbValueMax, and pcbValue arguments of SQLBindParameter are used as follows for output parameters:

  • rgbValue: During statement execution, points to the buffer where the value returned from the statement should be placed.

  • cbValueMax: For character and binary data, indicates the maximum length of the outgoing value that rgbValue points to, in bytes. For all other data types, cbValueMax is ignored, and the length of the value that rgbValue points to is determined by the length of the C data type specified in the fCType argument of SQLBindParameter.

    Note that ODBC null-terminates all character data, even if the data is truncated. Therefore, when an output parameter has character data, cbValueMax must be large enough to accept the maximum data value plus a null terminator (one additional byte for CHAR and VARCHAR parameters, or two additional bytes for NCHAR and NVARCHAR parameters).

  • pcbValue: Points to a buffer that contains one of the following after statement execution:

    • The actual length of the value that rgbValue points to (for all C types, not just character and binary data)

      Note: This is the length of the full parameter value, regardless of whether the value can fit in the buffer that rgbValue points to.

    • SQL_NULL_DATA for a null value

Example 2-6 Binding output parameters

This example shows how to prepare, bind, and execute a PL/SQL anonymous block. The anonymous block assigns bind parameter a the value 'abcde' and bind parameter b the value 123.

SQLPrepare prepares the anonymous block. SQLBindParameter binds the first parameter (a) as an output parameter of type SQL_VARCHAR and binds the second parameter (b) as an output parameter of type SQL_INTEGER. SQLExecute executes the anonymous block.

{
  SQLHSTMT      hstmt;
  char          aval[11];
  SQLLEN        aval_len;
  SQLINTEGER    bval;
  SQLLEN        bval_len;
 
  SQLAllocStmt(hdbc, &hstmt);
 
  SQLPrepare(hstmt,
        (SQLCHAR*)"begin :a := 'abcde'; :b := 123; end;",
        SQL_NTS);
 
  SQLBindParameter(hstmt, 1, SQL_PARAM_OUTPUT, SQL_C_CHAR, SQL_VARCHAR,
         10, 0, (SQLPOINTER)aval, sizeof(aval), &aval_len);
 
  SQLBindParameter(hstmt, 2, SQL_PARAM_OUTPUT, SQL_C_SLONG, SQL_INTEGER,
         0, 0, (SQLPOINTER)&bval, sizeof(bval), &bval_len);
 
  SQLExecute(hstmt);
  printf("aval = [%s] (length = %d), bval = %d\n", aval, (int)aval_len, bval);
}

Binding input/output parameters

For input/output parameters to and from PL/SQL in TimesTen, as noted for input parameters previously, use the fSqlType, cbColDef, and ibScale arguments (as applicable) of the ODBC SQLBindParameter function to specify data types.

In addition, the rgbValue, cbValueMax, and pcbValue arguments of SQLBindParameter are used as follows for input/output parameters:

  • rgbValue: This is first used before statement execution as described in "Binding input parameters". Then it is used during statement execution as described in the preceding section, "Binding output parameters". Note that for an input/output parameter, the outgoing value from a statement execution is the incoming value to the statement execution that immediately follows, unless that is overridden by the application. Also, for input/output values bound when you are using data-at-execution, the value of rgbValue serves as both the token that would be returned by the ODBC SQLParamData function and as the pointer to the buffer where the outgoing value is placed.

  • cbValueMax: For character and binary data, this is first used as described in "Binding input parameters". Then it is used as described in the preceding section, "Binding output parameters". For all other data types, cbValueMax is ignored, and the length of the value that rgbValue points to is determined by the length of the C data type specified in the fCType argument of SQLBindParameter.

    Note that ODBC null-terminates all character data, even if the data is truncated. Therefore, when an input/output parameter has character data, cbValueMax must be large enough to accept the maximum data value plus a null terminator (one additional byte for CHAR and VARCHAR parameters, or two additional bytes for NCHAR and NVARCHAR parameters).

  • pcbValue: This is first used before statement execution as described in "Binding input parameters". Then it is used after statement execution as described in the preceding section, "Binding output parameters".

Important:

For character and binary data, carefully consider the value you use for cbValueMax. A value that is smaller than the actual buffer size may result in spurious truncation warnings. A value that is greater than the actual buffer size may cause the ODBC driver to overwrite the rgbValue buffer, resulting in memory corruption.

Binding duplicate parameters in SQL statements

In TimesTen, multiple occurrences of the same parameter name in a SQL statement are considered to be distinct parameters. (This is consistent with Oracle Database support for binding duplicate parameters.)

Notes:

  • This discussion applies only to SQL statements issued directly from ODBC, not through PL/SQL, for example. (Regarding PL/SQL statements, see the next section "Binding duplicate parameters in PL/SQL".)

  • "TimesTen mode" for binding duplicate parameters, and the DuplicateBindMode connection attribute, are deprecated.

  • The use of "?" for parameters, not supported in Oracle Database, is supported by TimesTen.

Consider this query:

SELECT * FROM employees
  WHERE employee_id < :a AND manager_id > :a AND salary < :b;

When parameter position numbers are assigned, a number is given to each parameter occurrence without regard to name duplication. The application must, at a minimum, bind a value for the first occurrence of each parameter name. For any subsequent occurrence of a given parameter name, the application has the following choices.

  • It can bind a different value for the occurrence.

  • It can leave the parameter occurrence unbound, in which case it takes the same value as the first occurrence.

In either case, each occurrence has a distinct parameter position number.

To use a different value for the second occurrence of a in the SQL statement above:

SQLBindParameter(..., 1, ...); /* first occurrence of :a */
SQLBindParameter(..., 2, ...); /* second occurrence of :a */
SQLBindParameter(..., 3, ...); /* occurrence of :b */

To use the same value for both occurrences of a:

SQLBindParameter(..., 1, ...); /* both occurrences of :a */
SQLBindParameter(..., 3, ...); /* occurrence of :b */

Parameter b is considered to be in position 3 regardless.

The SQLNumParams ODBC function returns 3 for the number of parameters in the example.

Binding duplicate parameters in PL/SQL

Discussion in the preceding section, "Binding duplicate parameters in SQL statements", does not apply to PL/SQL, which has its own semantics. In PL/SQL, you bind a value for each unique parameter name. An application executing the following block, for example, would bind only one parameter, corresponding to :a.

DECLARE
   x NUMBER;
   y NUMBER;
BEGIN
   x:=:a;
   y:=:a;
END;

An application executing the following block would also bind only one parameter:

BEGIN
   INSERT INTO tab1 VALUES(:a, :a);
END

And the same for the following CALL statement:

...CALL proc(:a, :a)...

An application executing the following block would bind two parameters, with :a as the first parameter and :b as the second parameter. The second parameter in each INSERT statement would take the same value as the first parameter in the first INSERT statement:

BEGIN
   INSERT INTO tab1 VALUES(:a, :a);
   INSERT INTO tab1 VALUES(:b, :a);
END

Considerations for floating point data

The BINARY_DOUBLE and BINARY_FLOAT data types store and retrieve the IEEE floating point values Inf, -Inf, and NaN. If an application uses a C language facility such as printf, scanf, or strtod that requires conversion to character data, the floating point values are returned as "INF", "-INF", and "NAN". These character strings cannot be converted back to floating point values.

Using SQL_WCHAR and SQL_WVARCHAR with a driver manager

Applications using the Windows driver manager may encounter errors from SQLBindParameter with SQL state S1004 (SQL data type out of range) when passing an fSqlType value of SQL_WCHAR or SQL_WVARCHAR. This problem can be avoided by passing one of the following values for fSqlType instead.

  • SQL_WCHAR_DM_SQLBINDPARAMETER_BYPASS instead of SQL_WCHAR

  • SQL_WVARCHAR_DM_SQLBINDPARAMETER_BYPASS instead of SQL_WVARCHAR

These type codes are semantically identical to SQL_WCHAR and SQL_WVARCHAR but avoid the error from the Windows driver manager. They can be used in applications that link with the driver manager or link directly with the TimesTen ODBC direct driver or ODBC client driver.

See "SQLBindParameter function" for information about that ODBC function.

Working with REF CURSORs

REF CURSOR is a PL/SQL concept, a handle to a cursor over a SQL result set that can be passed between PL/SQL and an application. In TimesTen, the cursor can be opened in PL/SQL then the REF CURSOR can be passed to the application. The results can be processed in the application using ODBC calls. This is an OUT REF CURSOR (an OUT parameter with respect to PL/SQL). The REF CURSOR is attached to a statement handle, enabling applications to describe and fetch result sets using the same APIs as for any result set.

Take the following steps to use a REF CURSOR. Assume a PL/SQL statement that returns a cursor through a REF CURSOR OUT parameter. Note that REF CURSORs use the same basic steps of prepare, bind, execute, and fetch as in the cursor example in "Preparing and executing queries and working with cursors".

  1. Prepare the PL/SQL statement, using SQLPrepare, to be associated with the first statement handle.

  2. Bind each parameter of the statement, using SQLBindParameter. When binding the REF CURSOR output parameter, use an allocated second statement handle as rgbValue, the pointer to the data buffer.

    The pcbValue, ibScale, cbValueMax, and pcbValue arguments are ignored for REF CURSORs.

    See "SQLBindParameter function" and "Binding output parameters" for information about these and other SQLBindParameter arguments.

  3. Call SQLBindCol to bind result columns to local variable storage.

  4. Call SQLExecute to execute the statement.

  5. Call SQLFetch to fetch the results. After a REF CURSOR is passed from PL/SQL to an application, the application can describe and fetch the results as it would for any result set.

  6. Use SQLFreeStmt to free the statement handle.

These steps are demonstrated in the example that follows. Refer to ODBC API reference documentation for details on these functions. See "PL/SQL REF CURSORs" in Oracle TimesTen In-Memory Database PL/SQL Developer's Guide for additional information about REF CURSORs.

Important:

For passing REF CURSORs between PL/SQL and an application, TimesTen supports only OUT REF CURSORs, from PL/SQL to the application, and supports a statement returning only a single REF CURSOR.

Example 2-7 Executing a query and working with a REF CURSOR

This example, using a REF CURSOR in a loop, demonstrates the basic steps of preparing a query, binding parameters, executing the query, binding results to local variable storage, and fetching the results. Error handling is omitted for simplicity. In addition to the ODBC functions summarized earlier, this example uses SQLAllocStmt to allocate memory for a statement handle.

refcursor_example(SQLHDBC hdbc)
{
  SQLCHAR*      stmt_text;
  SQLHSTMT      plsql_hstmt;
  SQLHSTMT      refcursor_hstmt;
  SQLINTEGER    deptid;
  SQLINTEGER    depts[3] = {10,30,40};
  SQLINTEGER    empid;
  SQLCHAR       lastname[30];
  SQLINTEGER    i;
 
  /* allocate 2 statement handles: one for the plsql statement and
   * one for the ref cursor */
  SQLAllocStmt(hdbc, &plsql_hstmt);
  SQLAllocStmt(hdbc, &refcursor_hstmt);
 
  /* prepare the plsql statement */
  stmt_text = (SQLCHAR*)
    "begin "
      "open :refc for "
        "select employee_id, last_name "
        "from employees "
        "where department_id = :dept; "
    "end;";
  SQLPrepare(plsql_hstmt, stmt_text, SQL_NTS);
 
  /* bind parameter 1 (:refc) to refcursor_hstmt */
  SQLBindParameter(plsql_hstmt, 1, SQL_PARAM_OUTPUT, SQL_C_REFCURSOR,
                   SQL_REFCURSOR, 0, 0, refcursor_hstmt, 0, 0);
 
  /* bind parameter 2 (:deptid) to local variable deptid */
  SQLBindParameter(plsql_hstmt, 2, SQL_PARAM_INPUT, SQL_C_SLONG,
                   SQL_INTEGER, 0, 0, &deptid, 0, 0);
 
  /* loop through values for :deptid */
  for (i=0; i<3; i++)
  {
     deptid = depts[i];
 
     /* execute the plsql statement */
     SQLExecute(plsql_hstmt);
     /*
      * The result set is now attached to refcursor_hstmt.
      * Bind the result columns and fetch the result set.
      */
 
     /* bind result column 1 to local variable empid */
     SQLBindCol(refcursor_hstmt, 1, SQL_C_SLONG,
                (SQLPOINTER)&empid, 0, 0);
 
     /* bind result column 2 to local variable lastname */
     SQLBindCol(refcursor_hstmt, 2, SQL_C_CHAR,
                (SQLPOINTER)lastname, sizeof(lastname), 0);
 
     /* fetch the result set */
     while(SQLFetch(refcursor_hstmt) != SQL_NO_DATA_FOUND){
       printf("%d, %s\n", empid, lastname);
     }
 
     /* close the ref cursor statement handle */
     SQLFreeStmt(refcursor_hstmt, SQL_CLOSE);
  }
   
  /* drop both handles */
  SQLFreeStmt(plsql_hstmt, SQL_DROP);
  SQLFreeStmt(refcursor_hstmt, SQL_DROP);
}

Working with DML returning (RETURNING INTO clause)

You can use a RETURNING INTO clause, referred to as DML returning, with an INSERT, UPDATE, or DELETE statement to return specified items from a row that was affected by the action. This eliminates the need for a subsequent SELECT statement and separate round trip in case, for example, you want to confirm what was affected by the action.

With ODBC, DML returning is limited to returning items from a single-row operation. The clause returns the items into a list of output parameters. Bind the output parameters as discussed in "Binding parameters and executing statements".

SQL syntax and restrictions for the RETURNING INTO clause in TimesTen are documented as part of "INSERT", "UPDATE", and "DELETE" in Oracle TimesTen In-Memory Database SQL Reference.

Refer to "RETURNING INTO Clause" in Oracle Database PL/SQL Language Reference for details about DML returning.

Example 2-8 DML returning

This example is adapted from Example 2-10, with bold text highlighting key portions.

void
update_example(SQLHDBC hdbc)
{
   SQLCHAR*      stmt_text;
   SQLHSTMT      hstmt;
   SQLINTEGER    raise_pct;
   char          hiredate_str[30];
   char          last_name[30];
   SQLLEN        hiredate_len;
   SQLLEN        numrows;

   /* allocate a statement handle */
   SQLAllocStmt(hdbc, &hstmt);

   /* prepare an update statement to give a raise to one employee hired
      before a given date and return that employee's last name */
   stmt_text = (SQLCHAR*)
     "update employees "
     "set salary = salary * ((100 + :raise_pct) / 100.0) "
     "where hire_date < :hiredate and rownum = 1 returning last_name into "
                       ":last_name";
   SQLPrepare(hstmt, stmt_text, SQL_NTS);

   /* bind parameter 1 (:raise_pct) to variable raise_pct */
   SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG,
                    SQL_DECIMAL, 0, 0, (SQLPOINTER)&raise_pct, 0, 0);

   /* bind parameter 2 (:hiredate) to variable hiredate_str */
   SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
                    SQL_TIMESTAMP, 0, 0, (SQLPOINTER)hiredate_str,
                    sizeof(hiredate_str), &hiredate_len);
   /* bind parameter 3 (:last_name) to variable last_name */
   SQLBindParameter(hstmt, 3, SQL_PARAM_OUTPUT, SQL_C_CHAR,
                    SQL_VARCHAR, 30, 0, (SQLPOINTER)last_name,
                    sizeof(last_name), NULL);
   /* set parameter values to give a 10% raise to an employee hired before
    * January 1, 1996. */
   raise_pct = 10;
   strcpy(hiredate_str, "1996-01-01");
   hiredate_len = SQL_NTS;

   /* execute the update statement */
   SQLExecute(hstmt);

   /* tell us who the lucky person is */
   printf("Gave raise to %s.\n", last_name );

   /* drop the statement handle */
   SQLFreeStmt(hstmt, SQL_DROP);

   /* commit the changes */
   SQLTransact(henv, hdbc, SQL_COMMIT);

}

This returns "King" as the recipient of the raise.

Working with rowids

Each row in a database table has a unique identifier known as its rowid. An application can retrieve the rowid of a row from the ROWID pseudocolumn. Rowids can be represented in either binary or character format.

An application can specify literal rowid values in SQL statements, such as in WHERE clauses, as CHAR constants enclosed in single quotes.

As noted in Table 2-2, the ODBC SQL type SQL_ROWID corresponds to the SQL type ROWID.

For parameters and result set columns, rowids are convertible to and from the C types SQL_C_BINARY, SQL_C_WCHAR, and SQL_C_CHAR. SQL_C_CHAR is the default C type for rowids. The size of a rowid would be 12 bytes as SQL_C_BINARY, 18 bytes as SQL_C_CHAR, and 36 bytes as SQL_C_WCHAR.

Refer to "ROWID data type" and "ROWID pseudocolumn" in Oracle TimesTen In-Memory Database SQL Reference for additional information about rowids and the ROWID data type, including usage and life.

Note:

TimesTen does not support the PL/SQL type UROWID.

Working with LOBs

TimesTen Classic supports LOBs (large objects). This includes CLOBs (character LOBs), NCLOBs (national character LOBs), and BLOBs (binary LOBs).

This section provides a brief overview of LOBs and discusses their use in ODBC, covering the following topics:

You can also refer to the following:

About LOBs

A LOB is a large binary object (BLOB) or character object (CLOB or NCLOB). In TimesTen, a BLOB can be up to 16 MB and a CLOB or NCLOB up to 4 MB. LOBs in TimesTen have essentially the same functionality as in Oracle Database, except as noted otherwise. (See the next section, "Differences between TimesTen LOBs and Oracle Database LOBs".)

LOBs may be either persistent or temporary. A persistent LOB exists in a LOB column in the database. A temporary LOB exists only within an application. There are circumstances where a temporary LOB is created implicitly. For example, if a SELECT statement selects a LOB concatenated with an additional string of characters, TimesTen creates a temporary LOB to contain the concatenated data. In TimesTen ODBC, any temporary LOBs are managed implicitly.

Temporary LOBs are stored in the TimesTen temporary data region.

Differences between TimesTen LOBs and Oracle Database LOBs

Be aware of the following:

  • A key difference between the TimesTen LOB implementation and the Oracle Database implementation is that in TimesTen, a LOB used in an application does not remain valid past the end of the transaction. All such LOBs are invalidated after a commit or rollback, whether explicit or implicit. This includes after any DDL statement.

  • TimesTen does not support BFILEs, SecureFiles, array reads and writes for LOBs, or callback functions for LOBs.

  • TimesTen does not support binding arrays of LOBs.

  • TimesTen does not support batch processing of LOBs.

  • Relevant to BLOBs, there are differences in the usage of hexadecimal literals in TimesTen. see the description of HexadecimalLiteral in "Constants" in Oracle TimesTen In-Memory Database SQL Reference.

LOB programming interfaces

There are three programmatic approaches, as follows, for accessing LOBs from TimesTen in a C or C++ program.

  • Simple data interface (ODBC, OCI, Pro*C/C++, TTClasses): Use binds and defines, as with other scalar types, to transfer LOB data in a single chunk.

  • Piecewise data interface (ODBC): Use advanced forms of binds and defines to transfer LOB data in multiple pieces. This is sometimes referred to as streaming or using data-at-exec (at program execution time). TimesTen supports the piecewise data interface through polling loops to go piece-by-piece through the LOB data. (Another piecewise approach, using callback functions, is supported by Oracle Database but not by TimesTen.)

  • LOB locator interface (OCI, Pro*C/C++): Select LOB locators using SQL then access LOB data through APIs that are similar conceptually to those used in accessing a file system. Using the LOB locator interface, you can work with LOB data in pieces or in single chunks. (See "LOBs in TimesTen OCI" and "LOBs in TimesTen Pro*C/C++".)

The LOB locator interface offers the most utility if it is feasible for you to use it.

Using the LOB simple data interface in ODBC

The simple data interface enables applications to access LOB data by binding and defining, just as with other scalar types. For the simple data interface in ODBC, use SQLBindParameter to bind parameters and SQLBindCol to define result columns. The application can bind or define using a SQL type that is compatible with the corresponding variable type, as follows.

  • For BLOB data, use SQL type SQL_LONGVARBINARY and C type SQL_C_BINARY.

  • For CLOB data, use SQL type SQL_LONGVARCHAR and C type SQL_C_CHAR.

  • For NCLOB data, use SQL type SQL_WLONGVARCHAR and C type SQL_C_WCHAR.

SQLBindParameter and SQLBindCol calls for LOB data would be very similar to such calls for other data types, discussed earlier in this chapter.

Note:

Binding a CLOB or NCLOB with a C type of SQL_C_BINARY is prohibited.

Using the LOB piecewise data interface in ODBC

The piecewise interface enables applications to access LOB data in portions, piece by piece. An application binds parameters or defines results similarly to how those actions are performed for the simple data interface, but indicates that the data is to be provided or retrieved at program execution time ("at exec"). In TimesTen, you can implement the piecewise data interface through a polling loop that is repeated until all the LOB data has been read or written.

For the piecewise data interface in ODBC, use SQLParamData with SQLPutData in a polling loop to bind parameters, as shown in Example 2-9 below, and SQLGetData in a polling loop to retrieve results. See the preceding section, "Using the LOB simple data interface in ODBC", for information about supported SQL and C data types for BLOBs, CLOBs, and NCLOBs.

Note:

Similar piecewise data access has already been supported for the various APIs in previous releases of TimesTen, for var data types.

Example 2-9 Using SQLPutData, ODBC piecewise data interface

This program excerpt uses SQLPutData with SQLParamData in a polling loop to insert LOB data piece-by-piece into the database. The CLOB column contains the value "123ABC" when the code is executed.

...
  /* create a table */
  create_stmt = "create table clobtable ( c clob )";
  rc = SQLExecDirect(hstmt, (SQLCHAR *)create_stmt, SQL_NTS);
  if(rc != SQL_SUCCESS){/* ...error handling... */}
 
  /* initialize an insert statement */
  insert_stmt = "insert into clobtable values(?)";
  rc = SQLPrepare(hstmt, (SQLCHAR *)insert_stmt, SQL_NTS);
  if(rc != SQL_SUCCESS){/* ...error handling... */}
 
  /* bind the parameter and specify that we will be using
   * SQLParamData/SQLPutData */
  rc = SQLBindParameter(
    hstmt,            /* statement handle */
    1,                /* colnum number */
    SQL_PARAM_INPUT,  /* param type */
    SQL_C_CHAR,       /* C type */
    SQL_LONGVARCHAR,  /* SQL type (ignored) */
    2,                /* precision (ignored) */
    0,                /* scale (ignored) */
    0,                /* putdata token */
    0,                /* ignored */
    &pcbvalue);       /* indicates use of SQLPutData */
  if(rc != SQL_SUCCESS){/* ...error handling... */}
 
  pcbvalue = SQL_DATA_AT_EXEC;
 
  /* execute the statement -- this should return SQL_NEED_DATA */
  rc = SQLExecute(hstmt);
  if(rc != SQL_NEED_DATA){/* ...error handling... */}
 
  /* while we still have parameters that need data... */
  while((rc = SQLParamData(hstmt, &unused)) == SQL_NEED_DATA){
 
    memcpy(char_buf, "123", 3);
    rc = SQLPutData(hstmt, char_buf, 3);
    if(rc !=  SQL_SUCCESS){/* ...error handling... */}
 
    memcpy(char_buf, "ABC", 3);
    rc = SQLPutData(hstmt, char_buf, 3);
    if(rc !=  SQL_SUCCESS){/* ...error handling... */}
 
  }
...

Passthrough LOBs in ODBC

Passthrough LOBs, which are LOBs in Oracle Database accessed through TimesTen, are exposed as TimesTen LOBs and are supported by TimesTen in much the same way that any TimesTen LOB is supported, but note the following:

  • TimesTen LOB size limitations do not apply to storage of LOBs in the Oracle database through passthrough.

  • As with TimesTen local LOBs, a passthrough LOB used in an application does not remain valid past the end of the transaction.

Making and committing changes to the database

Autocommit is enabled by default (according to the ODBC specification), so that any DML change you make, such as an update, insert, or delete, is committed automatically. It is recommended, however, that you disable this feature and commit (or roll back) your changes explicitly. Use the SQL_AUTOCOMMIT option in a SQLSetConnectOption call to accomplish this:

rc = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);

With autocommit disabled, you can commit or roll back a transaction using the SQLTransact ODBC function, such as in the following example to commit:

rc = SQLTransact(henv, hdbc, SQL_COMMIT);

Refer to ODBC API reference documentation for details about these functions.

Notes:

  • Autocommit mode applies only to the top-level statement executed by SQLExecute or SQLExecDirect. There is no awareness of what occurs inside the statement, and therefore no capability for intermediate autocommits of nested operations.

  • All open cursors on the connection are closed upon transaction commit or rollback in TimesTen.

  • The SQLRowCount function can be used to return information about SQL operations. For UPDATE, INSERT, and DELETE statements, the output argument returns the number of rows affected. See "Managing cache groups" regarding special TimesTen functionality. Refer to ODBC API reference documentation for general information about SQLRowCount and its arguments.

You can refer to "Transaction overview" in Oracle TimesTen In-Memory Database Operations Guide for additional information about transactions.

Example 2-10 Updating the database and committing the change

This example prepares and executes a statement to give raises to selected employees, then manually commits the changes. Assume autocommit has been previously disabled.

update_example(SQLHDBC hdbc)
{
  SQLCHAR*      stmt_text;
  SQLHSTMT      hstmt;
  SQLINTEGER    raise_pct;
  char          hiredate_str[30];
  SQLLEN        hiredate_len;
  SQLLEN        numrows;
 
  /* allocate a statement handle */
  SQLAllocStmt(hdbc, &hstmt);
 
  /* prepare an update statement to give raises to employees hired before a
   * given date */
  stmt_text = (SQLCHAR*)
    "update employees "
    "set salary = salary * ((100 + :raise_pct) / 100.0) "
    "where hire_date < :hiredate";
  SQLPrepare(hstmt, stmt_text, SQL_NTS);
 
  /* bind parameter 1 (:raise_pct) to variable raise_pct */
  SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG,
                   SQL_DECIMAL, 0, 0, (SQLPOINTER)&raise_pct, 0, 0);
 
  /* bind parameter 2 (:hiredate) to variable hiredate_str */
  SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
                   SQL_TIMESTAMP, 0, 0, (SQLPOINTER)hiredate_str,
                   sizeof(hiredate_str), &hiredate_len);
 
  /* set parameter values to give a 10% raise to employees hired before
   * January 1, 1996. */
  raise_pct = 10;
  strcpy(hiredate_str, "1996-01-01");
  hiredate_len = SQL_NTS;
 
  /* execute the update statement */
  SQLExecute(hstmt);
 
  /* print the number of employees who got raises  */
  SQLRowCount(hstmt, &numrows);
  printf("Gave raises to %d employees.\n", numrows);
 
  /* drop the statement handle */
  SQLFreeStmt(hstmt, SQL_DROP);

  /* commit the changes */
  SQLTransact(henv, hdbc, SQL_COMMIT);

}

Using additional TimesTen data management features

Preceding sections discussed key features for managing TimesTen data. This section covers the additional features listed here.

Using CALL to execute procedures and functions

TimesTen Classic supports each of the following syntax formats from any of its programming interfaces to call PL/SQL procedures (procname) or PL/SQL functions (funcname) that are standalone or part of a package, or to call TimesTen built-in procedures (procname).

CALL procname[(argumentlist)]

CALL funcname[(argumentlist)] INTO :returnparam

CALL funcname[(argumentlist)] INTO ?

TimesTen ODBC also supports each of the following syntax formats:

{ CALL procname[(argumentlist)] }

{ ? = [CALL] funcname[(argumentlist)] }

{ :returnparam = [CALL] funcname[(argumentlist)] }

The following ODBC example calls the TimesTen ttCkpt built-in procedure.

rc = SQLExecDirect (hstmt, (SQLCHAR*) "call ttCkpt",SQL_NTS);

These examples call a PL/SQL procedure myproc with two parameters:

rc = SQLExecDirect(hstmt, (SQLCHAR*) "{ call myproc(:param1, :param2) }",SQL_NTS);

rc = SQLExecDirect(hstmt, (SQLCHAR*) "{ call myproc(?, ?) }",SQL_NTS);

The following shows several ways to call a PL/SQL function myfunc:

rc = SQLExecDirect (hstmt, (SQLCHAR*) "CALL myfunc() INTO :retparam",SQL_NTS);

rc = SQLExecDirect (hstmt, (SQLCHAR*) "CALL myfunc() INTO ?",SQL_NTS);

rc = SQLExecDirect (hstmt, (SQLCHAR*) "{ :retparam = myfunc() }",SQL_NTS);

rc = SQLExecDirect (hstmt, (SQLCHAR*) "{ ? = myfunc() }",SQL_NTS);

See "CALL" in Oracle TimesTen In-Memory Database SQL Reference for details about CALL syntax.

Notes:

  • A user's own procedure takes precedence over a TimesTen built-in procedure with the same name, but it is best to avoid such naming conflicts.

  • TimesTen does not support using SQL_DEFAULT_PARAM with SQLBindParameter for a CALL statement.

Setting a timeout or threshold for executing SQL statements

TimesTen offers two ways to limit the time for SQL statements or procedure calls to execute, by either setting a timeout duration or setting a threshold duration. This applies to any SQLExecute, SQLExecDirect, or SQLFetch call.

If a timeout duration is reached, the statement stops executing and an error is thrown. If a threshold duration is reached, a warning is written to the support log but execution continues.

This section covers the following topics:

Setting a timeout duration for SQL statements

To control how long SQL statements should execute before timing out, you can set the SQL_QUERY_TIMEOUT option using a SQLSetStmtOption or SQLSetConnectOption call to specify a timeout value, in seconds. A value of 0 indicates no timeout. Despite the name, this timeout value applies to any executable SQL statement, not just queries.

In TimesTen, you can specify this timeout value for a connection, and therefore any statement on the connection, by using either the SQLQueryTimeout general connection attribute (in seconds) or the SQLQueryTimeoutMsec general connection attribute (in milliseconds). The default value of each is 0, for no timeout. (Also see "SQLQueryTimeout" and "SQLQueryTimeoutMsec" in Oracle TimesTen In-Memory Database Reference.)

Despite the names, these timeout values apply to any executable SQL statement, not just queries.

A call to SQLSetConnectOption with the SQL_QUERY_TIMEOUT option overrides any previous query timeout setting. A call to SQLSetStmtOption with the SQL_QUERY_TIMEOUT option overrides the connection setting for the particular statement.

The query timeout limit has effect only when a SQL statement is actively executing. A timeout does not occur during commit or rollback. For transactions that update, insert, or delete a large number of rows, the commit or rollback phases may take a long time to complete. During that time the timeout value is ignored.

See "Choose SQL and PL/SQL timeout values" in Oracle TimesTen In-Memory Database Operations Guide for considerations regarding the SQL query timeout with respect to other timeout settings.

Note:

If both a lock timeout value and a SQL query timeout value are specified, the lesser of the two values causes a timeout first. Regarding lock timeouts, you can refer to "ttLockWait" (built-in procedure) or "LockWait" (general connection attribute) in Oracle TimesTen In-Memory Database Reference, or to "Check for deadlocks and timeouts" in Oracle TimesTen In-Memory Database Troubleshooting Guide.

Setting a threshold duration for SQL statements

You can configure TimesTen to write a warning to the support log when the execution of a SQL statement exceeds a specified time duration, in seconds. Execution continues and is not affected by the threshold.

By default, the application obtains the threshold from the QueryThreshold general connection attribute setting (refer to "QueryThreshold" in Oracle TimesTen In-Memory Database Reference). The default value is 0, for no warnings. Setting the TT_QUERY_THRESHOLD option in a SQLSetConnectOption call overrides the connection attribute setting for the current connection. Despite the name, the threshold applies to any executable SQL statement.

To set the threshold with SQLSetConnectOption:

RETCODE SQLSetConnectOption(hdbc, TT_QUERY_THRESHOLD, seconds);

Setting the TT_QUERY_THRESHOLD option in a SQLSetStmtOption call overrides the connection attribute setting, and any setting through SQLSetConnectOption, for the statement. It applies to SQL statements executed using the ODBC statement handle.

To set the threshold with SQLSetStmtOption:

RETCODE SQLSetStmtOption(hstmt, TT_QUERY_THRESHOLD, seconds);

You can retrieve the current value of TT_QUERY_THRESHOLD by using the SQLGetConnectOption or SQLGetStmtOption ODBC function:

RETCODE SQLGetConnectOption(hdbc, TT_QUERY_THRESHOLD, paramvalue);

RETCODE SQLGetStmtOption(hstmt, TT_QUERY_THRESHOLD, paramvalue);

Features for use with TimesTen Cache

This section discusses features related to the use of TimesTen Cache in TimesTen Classic:

See Oracle TimesTen Application-Tier Database Cache User's Guide for information about TimesTen Cache.

See "PassThrough" in Oracle TimesTen In-Memory Database Reference for information about that general connection attribute. See "Setting a passthrough level" in Oracle TimesTen Application-Tier Database Cache User's Guide for information about passthrough settings.

Setting temporary passthrough level with the ttOptSetFlag built-in procedure

TimesTen provides the ttOptSetFlag built-in procedure for setting various flags, including the PassThrough flag to temporarily set the passthrough level. You can use ttOptSetFlag to set PassThrough in a C application as in the following example that sets the passthrough level to 1. The setting affects all statements that are prepared until the end of the transaction.

rc = SQLExecDirect (hstmt, "call ttOptSetFlag ('PassThrough', 1)",SQL_NTS);

See "ttOptSetFlag" in Oracle TimesTen In-Memory Database Reference for more information about that built-in procedure.

Determining passthrough status

You can call the SQLGetStmtOption ODBC function with the TT_STMT_PASSTHROUGH_TYPE statement option to determine whether a SQL statement is to be executed in the TimesTen database or passed through to the Oracle database for execution. This is shown in the following example.

rc = SQLGetStmtOption(hStmt, TT_STMT_PASSTHROUGH_TYPE, &passThroughType);

You can make this call after preparing the SQL statement. It is useful with PassThrough settings of 1 or 2, where the determination of whether a statement is actually passed through is not made until compilation time. If TT_STMT_PASSTHROUGH_NONE is returned, the statement is to be executed in TimesTen. If TT_STMT_PASSTHROUGH_ORACLE is returned, the statement is to be passed through to Oracle Database for execution.

See "Setting a passthrough level" in Oracle TimesTen Application-Tier Database Cache User's Guide for information about PassThrough settings.

Note:

TT_STMT_PASSTHROUGH_TYPE is supported with SQLGetStmtOption only, not with SQLSetStmtOption.

Managing cache groups

In TimesTen Cache, following the execution of a FLUSH CACHE GROUP, LOAD CACHE GROUP, REFRESH CACHE GROUP, or UNLOAD CACHE GROUP statement, the ODBC function SQLRowCount returns the number of cache instances that were flushed, loaded, refreshed, or unloaded.

For related information, see "Determining the number of cache instances affected by an operation" in Oracle TimesTen Application-Tier Database Cache User's Guide.

Refer to ODBC API reference documentation for general information about SQLRowCount.

Setting globalization options

TimesTen extensions to ODBC enable an application to set options for linguistic sorts, length semantics for character columns, and error reporting during character set conversion. These options can be used in a call to SQLSetConnectOption. The options are defined in the timesten.h file (noted in "TimesTen include files").

For more information about linguistic sorts, length semantics, and character sets, see "Globalization Support" in Oracle TimesTen In-Memory Database Operations Guide.

This section includes the following TimesTen ODBC globalization options.

TT_NLS_SORT

This option specifies the collating sequence used for linguistic comparisons. See "Monolingual linguistic sorts" and "Multilingual linguistic sorts" in Oracle TimesTen In-Memory Database Operations Guide for supported linguistic sorts.

It takes a string value. The default is "BINARY".

Also see the description of the NLS_SORT general connection attribute, which has the same functionality, in "NLS_SORT" in Oracle TimesTen In-Memory Database Reference. Note that TT_NLS_SORT, being a runtime option, takes precedence over the NLS_SORT connection attribute.

TT_NLS_LENGTH_SEMANTICS

This option specifies whether byte or character semantics is used. The possible values are as follows.

  • TT_NLS_LENGTH_SEMANTICS_BYTE (default)

  • TT_NLS_LENGTH_SEMANTICS_CHAR

Also see the description of the NLS_LENGTH_SEMANTICS general connection attribute, which has the same functionality, in "NLS_LENGTH_SEMANTICS" in Oracle TimesTen In-Memory Database Reference. Note that TT_NLS_LENGTH_SEMANTICS, being a runtime option, takes precedence over the NLS_LENGTH_SEMANTICS connection attribute.

TT_NLS_NCHAR_CONV_EXCP

This option specifies whether an error is reported when there is data loss during an implicit or explicit character type conversion between NCHAR or NVARCHAR2 data and CHAR or VARCHAR2 data during SQL operations. The option does not apply to conversions done by ODBC as a result of binding.

The possible values are:

  • TRUE: Errors during conversion are reported.

  • FALSE: Errors during conversion are not reported (default).

Also see the description of the NLS_NCHAR_CONV_EXCP general connection attribute, which has the same functionality, in "NLS_NCHAR_CONV_EXCP" in Oracle TimesTen In-Memory Database Reference. Note that TT_NLS_NCHAR_CONV_EXCP, being a runtime option, takes precedence over the NLS_NCHAR_CONV_EXCP connection attribute.

Features for use with replication

For applications that employ replication, you can improve performance by using parallel replication, which uses multiple threads acting in parallel to replicate and apply transactional changes to databases in a replication scheme. TimesTen Classic supports the following types of parallel replication:

  • Automatic parallel replication (ReplicationApplyOrdering=0): Parallel replication over multiple threads that automatically enforces transactional dependencies and all changes applied in commit order. This is the default.

  • Automatic parallel replication with disabled commit dependencies (ReplicationApplyOrdering=2): Parallel replication over multiple threads that automatically enforces transactional dependencies, but does not enforce transactions to be committed in the same order on the subscriber database as on the master database. In this mode, you can optionally specify replication tracks.

See "Configuring parallel replication" in Oracle TimesTen In-Memory Database Replication Guide for additional information and usage scenarios.

In an ODBC application that uses parallel replication and specifies replication tracks, you can specify the track number for transactions on a connection through the TT_REPLICATION_TRACK connection option, as noted in "Option support for ODBC 2.5 SQLSetConnectOption and SQLGetConnectOption". (Alternatively, use the general connection attribute ReplicationTrack or the ALTER SESSION parameter REPLICATION_TRACK.)

Handling Errors

This section includes the following topics:

Checking for errors

An application should check for errors and warnings on every call. This saves considerable time and effort during development and debugging. The sample applications provided with TimesTen show examples of error checking. See "TimesTen Quick Start and sample applications".

Errors can be checked using either the TimesTen error code (error number) or error string, as defined in the installation_dir/include/tt_errCode.h file. Entries are in the following format:

#define tt_ErrMemoryLock             712

For a description of each message, see "List of errors and warnings" in Oracle TimesTen In-Memory Database Error Messages and SNMP Traps.

After calling an ODBC function, check the return code. If the return code is not SQL_SUCCESS, use an error-handling routine that calls the ODBC function SQLError to retrieve the errors on the relevant ODBC handle. A single ODBC call may return multiple errors. The application should be written to return all errors by repeatedly calling the SQLError function until all errors are read from the error stack. Continue calling SQLError until the return code is SQL_NO_DATA_FOUND. (SQL_NO_DATA_FOUND is defined in sqlext.h, which is included by timesten.h.)

Refer to ODBC API reference documentation for details about the SQLError function and its arguments.

For more information about writing a function to handle standard ODBC errors, see "Retrieving errors and warnings" in Oracle TimesTen In-Memory Database Error Messages and SNMP Traps.

Example 2-11 Checking an ODBC function call for errors

This example shows that after a call to SQLAllocConnect, you can check for an error condition. If one is found, an error message is displayed and program execution is terminated.

rc = SQLAllocConnect(henv, &hdbc);

if (rc != SQL_SUCCESS) {
  handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
  fprintf(stderr,
          "Unable to allocate a connection handle:\n%s\n",
          err_buf);
  exit(-1);
}

Error and warning levels

When operations are not completely successful, TimesTen can return fatal errors, non-fatal errors, or warnings.

Fatal errors

Fatal errors are those that make the database inaccessible until after error recovery. When a fatal error occurs, all database connections are required to disconnect. No further operations may complete. Fatal errors are indicated by TimesTen error codes 846 and 994. Error handling for these errors should be different from standard error handling. In particular, the application error-handling code should roll back the current transaction and disconnect from the database.

Also see "Recovering after fatal errors".

Non-fatal errors

Non-fatal errors include simple errors such as an INSERT statement that violates unique constraints. This category also includes some classes of application and process failures.

TimesTen returns non-fatal errors through the normal error-handling process. Application should check for errors and appropriately handle them.

When a database is affected by a non-fatal error, an error may be returned and the application should take appropriate action.

An application can handle non-fatal errors by modifying its actions or, in some cases, rolling back one or more offending transactions.

Warnings

TimesTen returns warnings when something unexpected occurs that you may want to know about. Here are some events that cause TimesTen to issue a warning:

  • Checkpoint failure

  • Use of a deprecated feature

  • Truncation of some data

  • Execution of a recovery process upon connect

  • Replication return receipt timeout

Application developers should have code that checks for warnings, as they can indicate application problems.

Abnormal termination

In some cases, such as process failure, no error is returned, but TimesTen automatically rolls back the transactions of the failed process.

Recovering after fatal errors

When fatal errors occur, TimesTen performs a full cleanup and recovery procedure:

  • Every connection to the database is invalidated. To avoid out-of-memory conditions in the server, applications are required to disconnect from the invalidated database. Shared memory from the old TimesTen instance is not freed until all active connections at the time of the error have disconnected. Inactive applications still connected to the old TimesTen instance may have to be manually terminated.

  • The database is recovered from the checkpoint and transaction log files upon the first subsequent initial connection.

  • The recovered database reflects the state of all durably committed transactions and possibly some transactions that were committed non-durably.

  • No uncommitted or rolled back transactions are reflected.

Retrying after transient errors (ODBC)

TimesTen automatically resolves most transient errors (which is particularly important for TimesTen Scaleout), but if your application detects the following SQLSTATE value, it is suggested to retry the current transaction:

  • TT005: Transient transaction failure due to unavailability of resource. Roll back the transaction and try it again.

Notes:

In ODBC 3.5, SQLSTATE is returned by the SQLGetDiagRec function or indicated in the SQL_DIAG_SQLSTATE field of the SQLGetDiagField function. In ODBC 2.5, SQLSTATE is returned by the SQLError function. This SQLSTATE may be encountered by any of the following functions. Unless indicated otherwise, these functions apply to either ODBC 2.5 or ODBC 3.5.

  • Catalog functions (such as SQLTables and SQLColumns)

  • SQLCancel

  • SQLCloseCursor (ODBC 3.5)

  • SQLDisconnect

  • SQLExecDirect

  • SQLExecute

  • SQLFetch

  • SQLFetchScroll (ODBC 3.5)

  • SQLFreeStmt (ODBC 2.5)

  • SQLGetData

  • SQLGetInfo

  • SQLPrepare

  • SQLPutData

  • SQLEndTran (ODBC 3.5)

  • SQLTransact (ODBC 2.5)

Using automatic client failover in your application

Automatic client failover is for use in High Availability scenarios, for either TimesTen Scaleout or TimesTen Classic. There are two scenarios for TimesTen Classic, one with active standby pair replication and one referred to as generic automatic client failover.

If there is a failure of the database or database element to which the client is connected, then failover (connection transfer) to an alternate database or database element occurs:

  • For TimesTen Scaleout, failover is to an element from a list returned by TimesTen of available elements in the grid.

  • For TimesTen Classic with active standby replication, failover is to the new active (original standby) database.

  • For TimesTen Classic using generic automatic client failover, where you can ensure that the schema and data are consistent on both databases, failover is to a database from a list that is configured in the client odbc.ini file.

    A typical use case for generic automatic failover is a set of databases using read-only caching, where each database has the same set of cached data. For example, if you have several read-only cache groups, then you would create the same read-only cache groups on all TimesTen Classic databases included in the list of failover servers. When the client connection fails over to an alternate TimesTen database, the cached data is consistent because TimesTen Cache automatically refreshes the data (as needed) from the Oracle database.

Applications are automatically reconnected to the new database or database element. TimesTen provides features that enable applications to be alerted when this happens, so they can take any appropriate action.

This section discusses the TimesTen implementation of automatic client failover as it applies to application developers, covering the following topics.

For TimesTen Scaleout, see "Client connection failover" in Oracle TimesTen In-Memory Database Scaleout User's Guide for additional information. For TimesTen Classic, see "Using automatic client failover" in Oracle TimesTen In-Memory Database Operations Guide.

Notes:

  • Automatic client failover applies only to client/server connections. The functionality described here does not apply to direct connections.

  • Automatic client failover is complementary to Oracle Clusterware in situations where Oracle Clusterware is used, though the two features are not dependent on each other. You can also refer to "Using Oracle Clusterware to Manage Active Standby Pairs" in Oracle TimesTen In-Memory Database Replication Guide for information about Oracle Clusterware.

Functionality of automatic client failover

If a database or database element to which a client is connected fails, failover to an alternate database or database element occurs. When failover occurs, be aware of the following:

  • The client has a new connection but using the same ODBC connection handle. No state from the original connection is preserved, however, other than the handle itself. The application must open new ODBC statement handles and descriptor handles.

  • If you register a failover callback function (see "Failover callback functions".), a failover listener thread will be created within the client process to listen for failover event and invoke the callback function.

All client statement handles from the original connection are marked as invalid. API calls on these statement handles generally return SQL_ERROR with distinctive failover error codes defined in tt_errCode.h:

  • Native error 30105 with SQL state 08006

  • Native error 47137

The exception to this is for SQLError, SQLFreeStmt, SQLGetDiagRec, and SQLGetDiagField calls (depending on your version of ODBC), which behave normally.

In addition, note the following:

  • The socket to the original database or database element is closed. There is no need to call SQLDisconnect. TimesTen performs the equivalent, cleaning up the connection handle and confirming resources are freed.

  • In connecting to the new TimesTen database or database element, the same connection string and DSN definition from the original connection request are used, with the appropriate server name.

  • It is up to the application to open new statement handles and reexecute necessary SQLPrepare calls.

  • If a failover has already occurred and the client is already connected to the new database or database element:

    • For TimesTen Scaleout, the next failover request results in an attempt to connect to the next element in the list that was returned by TimesTen at the time of the original connection.

    • For TimesTen Classic with active standby replication, the next failover request results in an attempt to reconnect to the original active database. If that fails, alternating attempts are made to connect to the two servers until there is a timeout, and the connection is blocked during this period.

    • For TimesTen Classic using generic automatic client failover, the next failover request results in an attempt to connect to the next database in the list that is configured in the client odbc.ini file. This could be the next database sequentially or one chosen at random from the list, according to the setting of the TTC_Random_Selection connection attribute, which is described in "Configuration of automatic client failover".

    The timeout value is according to the TimesTen client connection attribute TTC_Timeout (default 60 seconds). (Refer to "TTC_Timeout" in Oracle TimesTen In-Memory Database Reference for information about that attribute.)

  • Failover connections are created only as needed, not in advance.

During failover, TimesTen can optionally make callbacks to a user-defined function that you register. This function takes care of any custom actions you want to occur in a failover situation. (See "Failover callback functions".)

The following public connection options are propagated to the new connection. The corresponding general connection attribute is shown in parentheses where applicable. The TT_REGISTER_FAILOVER_CALLBACK option is used to register your callback function.

SQL_ACCESS_MODE
SQL_AUTOCOMMIT
SQL_TXN_ISOLATION (Isolation)
SQL_OPT_TRACE
SQL_QUIET_MODE
TT_PREFETCH_CLOSE
TT_CLIENT_TIMEOUT (TTC_TIMEOUT)
TT_REGISTER_FAILOVER_CALLBACK

The following options are propagated to the new connection if they were set through connection attributes or SQLSetConnectOption calls, but not if set through TimesTen built-in procedures or ALTER SESSION.

TT_NLS_SORT (NLS_SORT)
TT_NLS_LENGTH_SEMANTICS (NLS_LENGTH_SEMANTICS)
TT_NLS_NCHAR_CONV_EXCP (NLS_NCHAR_CONV_EXCP)
TT_DYNAMIC_LOAD_ENABLE (DynamicLoadEnable)
TT_DYNAMIC_LOAD_ERROR_MODE (DynamicLoadErrorMode)
TT_NO_RECONNECT_ON_FAILOVER (TTC_NoReconnectOnFailover)

The following options are propagated to the new connection if they were set on the connection handle.

SQL_QUERY_TIMEOUT
TT_PREFETCH_COUNT

See "Connection Attributes" in Oracle TimesTen In-Memory Database Reference for information about TimesTen connection attributes.

Note:

If you issue an ALTER SESSION statement anytime after the initial database connection, you must re-issue the statement after a failover.

Configuration of automatic client failover

Refer to "Configuring automatic client failover for TimesTen Classic" in Oracle TimesTen In-Memory Database Operations Guide or "Client connection failover" in the Oracle TimesTen In-Memory Database Scaleout User's Guide for complete details on managing client connection failover in TimesTen.

In TimesTen Classic, failover DSNs must be specifically configured through TTC_Server2 and TTC_Servern connection attributes.

Note:

Setting any of TTC_Server2, TTC_Server_DSN2, TTC_Servern, or TCP_Port2 implies that you intend to use automatic client failover. For the active standby pair scenario, it also means a new thread is created for your application to support the failover mechanism.

Be aware of these TimesTen connection attributes:

  • TTC_NoReconnectOnFailover: If this is set to 1 (enabled), TimesTen is instructed to do all the usual client failover processing except for the automatic reconnect. (For example, statement and connection handles are marked as invalid.) This is useful if the application does its own connection pooling or manages its own reconnection to the database after failover. The default value is 0 (reconnect). Also see "TTC_NoReconnectOnFailover" in Oracle TimesTen In-Memory Database Reference.

  • TTC_REDIRECT: If this is set to 0 and the initial connection attempt to the desired database or database element fails, then an error is returned and there are no further connection attempts. This does not affect subsequent failovers on that connection. Also see "TTC_REDIRECT" in Oracle TimesTen In-Memory Database Reference.

  • TTC_Random_Selection: For TimesTen Classic using generic automatic client failover, the default setting of 1 (enabled) specifies that when failover occurs, the client randomly selects an alternative server from the list provided in TTC_Servern attribute settings. If the client cannot connect to the selected server, it keeps redirecting until it successfully connects to one of the listed servers. With a setting of 0, TimesTen goes through the list of TTC_Servern servers sequentially. Also see "TTC_Random_Selection" in Oracle TimesTen In-Memory Database Reference.

Note:

If you set any of these in odbc.ini or the connection string, the settings are applied to the failover connection. They cannot be set as ODBC connection options or ALTER SESSION attributes.

Failover callback functions

If there are custom actions you would like to have occur when there is a failover, you can have TimesTen make a callback to a user-defined function for such actions. This function is called when the attempt to connect to the new database or database element begins, and again after the attempt to connect is complete. This function could be used, for example, to cleanly restore statement handles.

The function API is defined as follows.

typedef SQLRETURN (*ttFailoverCallbackFcn_t)
  (SQLHDBC,      /* hdbc    */
   SQLPOINTER,   /* foCtx   */
   SQLUINTEGER,  /* foType  */
   SQLUINTEGER); /* foEvent */

Where:

  • hdbc is the ODBC connection handle for the connection that failed.

  • foCtx is a pointer to an application-defined data structure, for use as needed.

  • foType is the type of failover. In TimesTen, the only supported value for this is TT_FO_SESSION, which results in the session being reestablished. This does not result in statements being re-prepared.

  • foEvent indicates the event that has occurred, with the following supported values:

    • TT_FO_BEGIN: Beginning failover.

    • TT_FO_ABORT: Failover failed. Retries were attempted for the interval specified by TTC_Timeout (minimum value 60 seconds for active standby failover) without success.

    • TT_FO_END: Successful end of failover.

    • TT_FO_ERROR: A failover connection failed but will be retried.

    Note that TT_FO_REAUTH is not supported by TimesTen client failover.

Use a SQLSetConnectOption call to set the TimesTen TT_REGISTER_FAILOVER_CALLBACK option to register the callback function, specifying an option value that is a pointer to a structure of C type ttFailoverCallback_t that is defined as follows in the timesten.h file and refers to the callback function.

typedef struct{
  SQLHDBC                 appHdbc;
  ttFailoverCallbackFcn_t callbackFcn;
  SQLPOINTER              foCtx;
} ttFailoverCallback_t;

Where:

  • appHdbc is the ODBC connection handle, and should have the same value as hdbc in the SQLSetConnectOption calling sequence. (It is required in the data structure due to driver manager implementation details, in case you are using a driver manager.)

  • callbackFcn specifies the callback function. (You can set this to NULL to cancel callbacks for the given connection. The failover would still happen, but the application would not be notified.)

  • foCtx is a pointer to an application-defined data structure, as in the function description earlier.

Set TT_REGISTER_FAILOVER_CALLBACK for each connection for which a callback is desired. The values in the ttFailoverCallback_t structure are copied when the SQLSetConnectOption call is made. The structure need not be kept by the application. If TT_REGISTER_FAILOVER_CALLBACK is set multiple times for a connection, the last setting takes precedence.

Notes:

  • Because the callback function executes asynchronously to the main thread of your application, it should generally perform only simple tasks, such as setting flags that are polled by the application. However, there is no such restriction if the application is designed for multithreading. In that case, the function could even make ODBC calls, for example, but it is only safe to do so if the foEvent value TT_FO_END has been received.

  • It is up to the application to manage the data pointed to by the foCtx setting.

Example 2-12 Failover callback function and registration

This example shows the following features.

  • A globally defined user structure type, FOINFO, and the structure variable foStatus of type FOINFO

  • A callback function, FailoverCallback(), that updates the foStatus structure whenever there is a failover

  • A registration function, RegisterCallback(), that does the following:

    • Declares a structure, failoverCallback, of type ttFailoverCallback_t.

    • Initializes foStatus values.

    • Sets the failoverCallback data values, consisting of the connection handle, a pointer to foStatus, and the callback function (FailoverCallback).

    • Registers the callback function with a SQLSetConnectOption call that sets TT_REGISTER_FAILOVER_CALLBACK as a pointer to failoverCallback.

/* user defined structure */
struct FOINFO
{
   int callCount;
   SQLUINTEGER lastFoEvent;
};
/* global variable passed into the callback function */
struct FOINFO foStatus;
 
/* the callback function */
SQLRETURN FailoverCallback (SQLHDBC hdbc,
                           SQLPOINTER pCtx,
                           SQLUINTEGER FOType,
                           SQLUINTEGER FOEvent)
{
   struct FOINFO* pFoInfo = (struct FOINFO*) pCtx;
 
   /* update the user defined data */
   if (pFoInfo != NULL)
   {
      pFoInfo->callCount ++;
      pFoInfo->lastFoEvent = FOEvent;
 
      printf ("Failover Call #%d\n", pFoInfo->callCount);
   }
 
   /* the ODBC connection handle */
   printf ("Failover HDBC : %p\n", hdbc);
 
   /* pointer to user data */
   printf ("Failover Data : %p\n", pCtx);
 
   /* the type */
   switch (FOType)
   {
       case TT_FO_SESSION:
       printf ("Failover Type : TT_FO_SESSION\n");
       break;
 
     default:
       printf ("Failover Type : (unknown)\n");
   }
 
   /* the event */
   switch (FOEvent)
   {
     case TT_FO_BEGIN:
       printf ("Failover Event: TT_FO_BEGIN\n");
       break;
 
     case TT_FO_END:
       printf ("Failover Event: TT_FO_END\n");
       break;
 
     case TT_FO_ABORT:
       printf ("Failover Event: TT_FO_ABORT\n");
       break;
 
     case TT_FO_REAUTH:
       printf ("Failover Event: TT_FO_REAUTH\n");
       break;
 
     case TT_FO_ERROR:
       printf ("Failover Event: TT_FO_ERROR\n");
       break;
 
     default:
       printf ("Failover Event: (unknown)\n");
   }
 
 return SQL_SUCCESS;
}
 
/* function to register the callback with the failover connection */
SQLRETURN RegisterCallback (SQLHDBC hdbc)
{
   SQLRETURN rc;
   ttFailoverCallback_t failoverCallback;
 
   /* initialize the global user defined structure */
   foStatus.callCount = 0;
   foStatus.lastFoEvent = -1;
 
   /* register the connection handle, callback and the user defined structure */
   failoverCallback.appHdbc = hdbc;
   failoverCallback.foCtx = &foStatus;
   failoverCallback.callbackFcn = FailoverCallback;
 
   rc = SQLSetConnectOption (hdbc, TT_REGISTER_FAILOVER_CALLBACK,
     (SQLULEN)&failoverCallback);
 
   return rc;
}

When a failover occurs, the callback function would produce output such as the following:

Failover Call #1
Failover HDBC : 0x8198f50
Failover Data : 0x818f8ac
Failover Type : TT_FO_SESSION
Failover Event: TT_FO_BEGIN

Application action in the event of failover

This section discusses these topics:

Application steps for failover

If you receive any of the error conditions noted in "Functionality of automatic client failover" in response to an operation in your application, then application failover is in progress. Perform these recovery actions:

  1. Issue a rollback on the connection. Until you do this, no further processing is possible on the connection.

  2. Clean up all objects from the previous connection. None of the state or objects associated with the previous connection are preserved, but proper cleanup through the relevant API calls is still strongly recommended.

  3. Assuming TTC_NoReconnectOnFailover=0 (the default), sleep briefly, as discussed in the next section, "Failover delay and retry settings". If TTC_NoReconnectOnFailover=1, then you must instead manually reconnect the application to an alternate database or database element.

  4. Recreate and reprepare all objects related to your connection.

  5. Restart any in-progress transactions from the beginning.

Failover delay and retry settings

The reconnection to another database or database element during automatic client failover may take some time. If your application attempts recovery actions before TimesTen has completed its client failover process, you may receive another failover error condition as listed in "Functionality of automatic client failover".

Therefore, your application should place all recovery actions within a loop with a short delay before each subsequent attempt, where the total number of attempts is limited. If you do not limit the number of attempts, the application may appear to hang if the client failover process does not complete successfully. For example, your recovery loop could use a retry delay of 100 milliseconds with a maximum number of retries limited to 100 attempts. The ideal values depend on your particular application and configuration.

Example 2-13 illustrates this point (as well as retrying transient errors, discussed in "Retrying after transient errors (ODBC)").

Example 2-13 Handling transient errors and client failover errors

/*
 * The following code snippet is a simple illustration of how you might handle 
 * the retrying of transient and connection failover errors in a C/ODBC 
 * application. In the interests of simplicity code that is not directly 
 * relevant to the example has been omitted (...). A real application
 * would of course be more complex.
 *
 * This example uses the ODBC 3.5 API.
 */
 
// define maximum retry counts and failover retry delay
#define  MAX_TE_RETRIES    30
#define  MAX_FO_RETRIES    100
#define  FO_RETRY_DELAY    100  // milliseconds   
 
// function return values
#define  SUCCESS           0
#define  FAILURE         (-1)
 
// constants for categorising errors
#define  ERR_OTHER         1
#define  ERR_TRANSIENT     2
#define  ERR_FAILOVER      3
 
// SQLSTATES and native errors
#define    SQLSTATE_TRANSIENT   "TT005"
#define    SQLSTATE_FAILOVER    "08006"
#define    NATIVE_FAILOVER1     47137
#define    NATIVE_FAILOVER2     30105
 
// SQL statements
SQLCHAR * sqlQuery = (SQLCHAR *)"SELECT ...";
SQLCHAR * sqlUpdate = (SQLCHAR *)"UPDATE ...";
 
// Database connection handle
SQLHDBC        dbConn = SQL_NULL_HDBC;
 
// Statement handles
SQLHSTMT      stmtQuery = SQL_NULL_HSTMT;
SQLHSTMT      stmtUpdate = SQL_NULL_HSTMT;
 
// ODBC return code
SQLRETURN rc;
 
// Retry counters
int teRetries; // transient errors
int foRetries; // failover errors
int foDelay = FO_RETRY_DELAY; // failover retry delay in ms
 
// Function to sleep for a specified number of milliseconds
void 
sleepMs( unsigned int ms)
{
    struct timespec rqtm, rmtm;
 
    rqtm.tv_sec = (time_t)(ms / 1000);
    rqtm.tv_nsec = (long)(ms % 1000000);
 
    while (  nanosleep( &rqtm, &rmtm )  )
        rqtm = rmtm;
} // sleepMs
 
// Function to check error stack for transient or failover errors.
// In a real application lots of other kinds of checking would also
// go in here to identify other errors of interest. We'd probably also
// log the errors to an error log.
int 
errorCategory( SQLHANDLE handle, SQLSMALLINT handleType )
{
    SQLRETURN rc;
    SQLSMALLINT i = 1;
    SQLINTEGER native_error;
    SQLCHAR sqlstate[LEN_SQLSTATE+1];
    SQLCHAR msgbuff[1024];
    SQLSMALLINT msglen;
 
    native_error = 0;
    sqlstate[0] = '\0';
    rc = SQLGetDiagRec( handleType, handle, i, sqlstate, &native_error,
                        msgbuff, sizeof(msgbuff), &msglen );
    while (   rc == SQL_SUCCESS  )
    {
        if (  strcmp( sqlstate, SQLSTATE_TRANSIENT ) == 0  )
            return ERR_TRANSIENT;
        else
        if (  native_error == NATIVE_FAILOVER1  )
            return ERR_FAILOVER;
        else
        if (  ( strcmp( sqlstate, SQLSTATE_FAILOVER ) == 0 ) &&
              (native_error == NATIVE_FAILOVER2)  )
            return ERR_FAILOVER;
        rc = SQLGetDiagRec( handleType, handle, ++i, sqlstate,
                            &native_error, msgbuff, sizeof(msgbuff),
                            &msglen );
    }
 
    return ERR_OTHER;
} // errorCategory
 
// Function to perform a rollback
void 
rollBack( SQLHDBC hDbc )
{
    SQLRETURN rc;
 
    rc = SQLEndTran( SQL_HANDLE_DBC, hDbc, SQL_ROLLBACK );
    // Report/log errors (a rollback failure is very, very bad).
    ...
} // rollBack
 
// Function to prepare all statements, bind parameters and bind
// columns.
int 
prepareAll( void )
{
    SQLRETURN rc;
 
    // Prepare the SQL statements and check for errors.
    rc = SQLPrepare( stmtQuery, sqlQuery, SQL_NTS );
    if (  rc != SQL_SUCCESS  )
    {
        rollBack( dbConn );
        return errorCategory( stmtQuery, SQL_HANDLE_STMT );
    }
    rc = SQLPrepare( stmtUpdate, sqlUpdate, SQL_NTS );
...
    // Bind parameters and colums
...
 
    return SUCCESS; // indicate success
} // prepareAll
 
// Function to execute a specific application transaction handling
// retries.
int 
txnSomeTransaction( ... )
{
    SQLRETURN rc;
    SQLLEN    rowcount = 0;
    int needReprepare = 0;
    int result;
 
    // Initialize retry counters
    teRetries = MAX_TE_RETRIES;
    foRetries = MAX_FO_RETRIES;
 
    // main retry loop
    while (  ( teRetries > 0 ) && ( foRetries > 0 )  )
    {
 
        // Do we need to re-prepare?
        while ( needReprepare && ( foRetries > 0 ) )
        {
            msSleep( retryDelay ); // delay before proceeding
            result = prepareAll();
            if (  result == SUCCESS  )
                needReprepare = 0;
            else
            if (  result != ERR_FAILOVER  )
                goto err;
            else
                foRetries--;
        }
 
        // First execute the query
 
        // Set input values for query
        ...
 
        // Execute query
        rc = SQLExecute( stmtQuery );
        if (  rc != SQL_SUCCESS  )
        {
            result = errorCategory( stmtQuery, SQL_HANDLE_STMT );
            rollBack( dbConn );
            switch (  result  )
            {
                case ERR_OTHER:
                    goto err;
                    break;
                case ERR_TRANSIENT:
                    teRetries--;
                    continue; // retry loop
                    break;
                case ERR_FAILOVER:
                    foRetries--;
                    needReprepare = 1;
                    continue; // retry loop
                    break;
            }
        }
 
        // Process results
        while (  (rc = SQLFetch( stmtQuery )) == SQL_SUCCESS  )
        {
            // process next row
            ...
        }
        if (  (rc != SQL_SUCCESS) && (rc != SQL_NO_DATA)  )
        {
            result = errorCategory( stmtQuery, SQL_HANDLE_STMT );
            rollBack( dbConn );
            switch (  result  )
            {
                case ERR_OTHER:
                    goto err;
                    break;
                case ERR_TRANSIENT:
                    teRetries--;
                    continue; // retry loop
                    break;
                case ERR_FAILOVER:
                    foRetries--;
                    needReprepare = 1;
                    continue; // retry loop
                    break;
            }
        }
 
        // Now execute the update
 
        // Set input values for update
        ...
 
        // Execute update
        rc = SQLExecute( stmtUpdate );
        if (  rc != SQL_SUCCESS  )
        {
            ...
        }
 
        // Check number of rows affected
        rc = SQLRowCount( stmtUpdate, &rowcount );
        if (  rc != SQL_SUCCESS  )
        {
            ...
        }
        // Check rowcount and handle unexpected cases
        if (  rowcount != 1  )
        {
            ...
        }
 
        // Finally, commit
        rc = SQLEndTran( SQL_HANDLE_DBC, dbConn, SQL_COMMIT );
        if (  rc != SQL_SUCCESS  )
        {
            ...
        }
 
        return SUCCESS; // all good
    } // retry loop
 
err:
    // if we get here, we ran out of retries or had some other non-retryable
    // error. Report/log it etc. then return failure
    ...
 
    return FAILURE;
} // txnSomeTransaction
 
// Main code
int
main ( int argc, char * argv[] )
{
    int status = 0; // final exit code
 
    ....
 
    // Open the connection  to the database and allocate statement handles
    ...
 
    // Disable auto-commit (this is essential)
    rc = SQLSetConnectAttr( dbConn,
                            SQL_ATTR_AUTOCOMMIT,
                            SQL_AUTOCOMMIT_OFF,
                            0 ); 
    ...
 
    // Prepare all statements, bind etc.
    if (  prepareAll() != SUCCESS  )
    {
        ...
    }
 
    // Do stuff until we are finished
    while (  ...  )
    {
        ...
        if (  txnSomeTransaction( ... ) != SUCCESS  )
        {
            ...
            goto fini;
        }
        ...
    }
 
fini:  // cleanup etc.
    // Release all resources (ODBC and non-ODBC)
    ...
    // Disconnect from database
    ...
 
    // Return final exit code
    return status;
} //main

Client routing API for TimesTen Scaleout

To increase performance, TimesTen Scaleout enables your client application to route connections to an element based on the key value for a hash distribution key. You provide a key value and TimesTen Scaleout returns an array of element IDs (or the replica set ID) where the database allocated that value. This enables the client application to connect to the element that stores the row with the specified key value, avoiding unnecessary communication between the element storing the row and the one connected to your application.

Note:

This feature is not supported with driver managers.

This section includes the next topics:

Creating a grid map and distribution

TimesTen Scaleout includes two new objects for client routing in the timesten.h file:

  • TTGRIDMAP: A grid map is a lookup table that maps the topology of a grid. You create a grid map by calling the ttGridMapCreate function with a valid ODBC connection. The function returns a handle to a TTGRIDMAP object.

    Note:

    • A TTGRIDMAP object is not strongly associated with the HDBC connection. Freeing either object does not free the other.

    • A grid map can be shared among many grid distributions and across application threads. Only one grid map is required per application process per database.

    Use the ttGridMapFree function to free a grid map.

  • TTGRIDDIST: A grid distribution is an ordered set of types and values that represent the distribution key columns of a table or tables. For distribution keys composed of multiple columns, the order of the types and values must be the same as for the distribution key columns of the table.

    You create a grid distribution by calling the ttGridDistCreate function with the C type, SQL type, length, scale, and precision of the distribution key columns of a table. The function returns a handle to a TTGRIDDIST object. Table 2-3 provides a brief summary of the arguments of the ttGridDistCreate function.

    Note:

    • A TTGRIDDIST object is not associated with a given table. You can use the same TTGRIDDIST object for any table that uses the same types and values in their distribution key columns.

    • A grid distribution cannot be shared across threads. However, multiple grid distributions in different threads can be created using the same grid map.

    Table 2-3 ttGridDistCreate arguments

    Argument Type Description

    hdbc

    SQLHDBC

    Connection handle

    map

    TTGRIDMAP

    Grid map handle

    cTypes[]

    SQLSMALLINT

    Array of C bind types in the same order as the distribution key columns

    sqlTypes[]

    SQLSMALLINT

    Array of SQL bind types in the same order as the distribution key columns

    precisions[]

    SQLULEN

    Array of precision values in the same order as the distribution key columns

    scales[]

    SQLSMALLINT

    Array of scale values in the same order as the distribution key columns

    maxSizes[]

    SQLLEN

    Array of maximum column size values in the same order as the distribution key columns

    nCols

    SQLUSMALLINT

    Number of columns in the distribution key

    *dist

    TTGRIDDIST

    Grid distribution handle (OUT)


    Note:

    The parameters for ttGridDistCreate are similar to those used in a subsequent SQLBindParameter ODBC call.

Use the ttGridDistFree function to free a grid distribution.

Example 2-14 Create a grid map and distribution

This example creates TTGRIDMAP and TTGRIDDIST objects. Then, the example calls the ttGridMapCreate function to create a grid map using an existing ODBC connection. Later, the example calls the ttGridDistCreate function to create a grid distribution based on a distribution key composed of two columns. Finally, the example frees the grid distribution and map with the ttGridDistFree and ttGridMapFree functions, respectively.

TTGRIDMAP map;
TTGRIDDIST dist;

ttGridMapCreate(hdbc, &map);

SQLSMALLINT cTypes[] = { SQL_C_LONG, SQL_C_CHAR };
SQLSMALLINT sqlTypes[] = { SQL_INTEGER, SQL_CHAR };
SQLLEN maxSizes[] = { 4, 20 };

ttGridDistCreate(hdbc, map, cTypes, sqlTypes, NULL, NULL, maxSizes, 2, &dist);

...

ttGridDistFree(hdbc, dist);
ttGridMapFree(hdbc, map);

Setting the distribution key values

With the grid map and distribution defined, set the key values in order to determine the elements in which they are allocated. Call the ttGridDistValueSet function to set the key value for one of the columns in the distribution key. For distribution keys composed of multiple columns, call this function once for every column in the distribution key. Table 2-4 provides a brief summary of the arguments of the ttGridDistValueSet function.

Table 2-4 ttGridDistValueSet arguments

Argument Type Description

hdbc

SQLHDBC

Connection handle

dist

TTGRIDDIST

Grid distribution handle

position

SQLSMALLINT

Position of the column in the distribution key

value

SQLPOINTER

Key value pointer

valueLen

SQLLEN

Length of the key value


Example 2-15 Set the distribution key values

The example first calls the ttGridDistClear function to clear any previously defined key values for the distribution key columns. Then, the example calls the ttGridDistValueSet function for every column in the distribution key and sets the key value for each column.

ttGridDistClear(hdbc, dist);

ttGridDistValueSet(hdbc, dist, 1, empId, sizeof(empId));
ttGridDistValueSet(hdbc, dist, 2, "SALES", SQL_NTS);

Getting the element location given a set of key values

Once you set the distribution key values, you can either call for the location of the key values by element IDs or replica set ID:

Get the element IDs

Call the ttGridDistElementGet function to obtain the corresponding element IDs that represent the location of the provided key values. The function returns an array of element IDs. The application is responsible for allocating the return array. The length of the array is based on the value of K-safety of the grid. For example, in a grid with K-safety set to 2, there must be at least two elements in the array. Table 2-5 provides a brief summary of the arguments of the ttGridDistElementGet function.

Table 2-5 ttGridDistElementGet arguments

Argument Type Description

hdbc

SQLHDBC

Connection handle

dist

TTGRIDDIST

Grid distribution handle

elemIds[]

SQLSMALLINT

Array of element IDs where the key values are allocated (IN/OUT)

elemIdSize

SQLSMALLINT

Value of K-safety


Example 2-16 Get the array of element IDs for the current key values

The example gets the array of element IDs associated with the current key values (set by the ttGridDistValueSet function) by calling the ttGridDistElementGet function.

SQLSMALLINT elementIds[2];

ttGridDistElementGet(hdbc, dist, elementIds, 2);

Note:

The elementIds array must be of a length equal or greater than the value of K-safety of the grid.

With the location of the set of key values available, your application can use the element IDs to select a connection to one of the elements, prepare a statement, bind values, and execute the statement.

Note:

The connection attempt can be subject to a failover event and the application may not connect to the expected element.

Example 2-17 shows the client routing API with most of its objects and functions in use.

Example 2-17 Client routing API

#include <timesten.h>

...

TTGRIDMAP map;
TTGRIDDIST dist;

/* Create a grid map using any existing connection. */
ttGridMapCreate(hdbc, &map);

/* The distribution key has two columns: one with TT_INTEGER as data type and
 * one with CHAR(20), in that order. Precision and scale are not necessary. */
SQLSMALLINT cTypes[] = { SQL_C_LONG, SQL_C_CHAR };
SQLSMALLINT sqlTypes[] = { SQL_INTEGER, SQL_CHAR };
SQLLEN maxSizes[] = { 4, 20 };

/* Create grid distribution from the grip map and the specified distribution
 * key column paremeters. */
ttGridDistCreate(hdbc, map, cTypes, sqlTypes, NULL, NULL, maxSizes, 2, &dist);

/* Execution loop. */
while ( ... ) 
{
      SQLSMALLINT elementIds[2];

      /* Clear the existing key values from the distribution map */
      ttGridDistClear(hdbc, dist);

      /* Set the key values for the grid distribution. */
      ttGridDistValueSet(hdbc, dist, 1, key1, sizeof(key1));
      ttGridDistValueSet(hdbc, dist, 2, key2, SQL_NTS);

      /* Get the corresponding element IDs for current key values*/
      ttGridDistElementGet(hdbc, dist, elementIds, 2);

      /* The application uses the element IDs to select a connection to 
       * one of the elements, prepare a statement, bind values, and execute 
       * the statement. */
      ...
}

/* Free the grid distribuion and map. */
ttGridDistFree(hdbc, dist);
ttGridMapFree(hdbc, map);

Example 2-18 shows a query that may help you associate an element ID with a connection string.

Example 2-18 Connection string for each element ID

The example assembles a connection string for each element of the database by querying the SYS.V$DISTRIBUTION_CURRENT system view. The connection string includes the TTC_REDIRECT=0 attribute to ensure a connection to the specified element or its replica. If the connection to all replicas fails, then a connection error is returned.

select 'TTC_REDIRECT=0;
TTC_SERVER='||hostexternaladdress||'/'||serverport,mappedelementid
 from SYS.V$DISTRIBUTION_CURRENT;
< TTC_REDIRECT=0;TTC_SERVER=ext-host3.example.com/6625, 1 >
< TTC_REDIRECT=0;TTC_SERVER=ext-host4.example.com/6625, 2 >
< TTC_REDIRECT=0;TTC_SERVER=ext-host5.example.com/6625, 3 >
< TTC_REDIRECT=0;TTC_SERVER=ext-host6.example.com/6625, 4 >
< TTC_REDIRECT=0;TTC_SERVER=ext-host7.example.com/6625, 5 >
< TTC_REDIRECT=0;TTC_SERVER=ext-host8.example.com/6625, 6 >
6 rows found.

Get the replica set ID

Call the ttGridDistReplicaGet function to obtain the corresponding replica set ID that represents the location of the provided key values. Table 2-6 provides a brief summary of the arguments of the ttGridDistReplicaGet function.

Table 2-6 ttGridDistReplicaGet arguments

Argument Type Description

hdbc

SQLHDBC

Connection handle

dist

TTGRIDDIST

Grid distribution handle

*replicaSetId

SQLSMALLINT

Replica set ID where the key values are allocated (OUT)


Example 2-19 Get the replica set ID for the current key values

The example gets the replica set ID associated with the current key values (set by the ttGridDistValueSet function) by calling the ttGridDistReplicaGet function.

SQLSMALLINT replicaSetId;

ttGridDistReplicaGet(hdbc, dist, replicaSetId);

As with element IDs in Example 2-18, you can use the replica set ID with the SYS.V$DISTRIBUTION_CURRENT system view to look up the communication parameters of the elements in that replica set.

Supported data types

The TTGRIDDIST object is created using the C types and SQL types available from ODBC. Table 2-7 shows the supported C types and SQL types with their corresponding Database SQL types.

Table 2-7 List of supported types

C types ODBC SQL types Database SQL types

SQL_C_TINYINT

SQL_TINYINT

TT_TINYINT

SQL_C_SMALLINT

SQL_SMALLINT

TT_SMALLINT

SQL_C_LONG

SQL_INTEGER

TT_INTEGER

SQL_C_BIGINT

SQL_BIGINT

TT_BIGINT

SQL_C_CHAR

SQL_CHAR

CHAR

SQL_C_CHAR

SQL_VARCHAR

VARCHAR, VARCHAR2

SQL_C_WCHAR

SQL_WCHAR

NCHAR

SQL_C_WCHAR

SQL_WVARCHAR

NVARCHAR

SQL_C_SQLT_NUM

SQL_DOUBLE

NUMBER

SQL_C_SQLT_NUM

SQL_DECIMAL

NUMBER(p,s)

SQL_C_SQLT_VNU

SQL_DOUBLE

NUMBER

SQL_C_SQLT_VNU

SQL_DECIMAL

NUMBER(p,s)


The TTGRIDDIST object supports all signed and unsigned data type variants. For example, it supports both SQL_C_SLONG and SQL_C_ULONG.

You can set NULL values by specifying SQL_NULL_DATA for the valueLen parameter of the ttGridDistValueSet function. The NULL value will always map to the same replica set or element IDs.

Restrictions

Client routing has these restrictions:

  • It does not have implicit connection or statement management.

  • It does not support date, time, or timestamp data types.

  • It does not support explicit type conversion. Applications must specify key values in canonical byte format.

  • It does not support character set conversion. It ignores the connection character set.

  • Changes in the topology of the grid require that applications free and recreate the grid map.

Failure modes

The client routing API may return an error in these scenarios:

  • Incorrect types and values to describe the distribution key columns of the table. In this case, the API will still compute an array of element IDs, but these may not correspond to the real location of the desired key values.

  • Unrecognized type codes. If you call the ttGridDistCreate function with unrecognized type codes, the function returns an error.

  • Not enough values set for the grid distribution. If you do not provide enough values for the distribution key through the ttGridDistValueSet function, then the ttGridDistElementGet or ttGridDistReplicaGet function would return an error.

  • Invalid size of the element IDs array. If you do not provide an array of at least the size of the value of K-safety, the ttGridDistElementGet function would return an error.