ODBCでの自動クライアント・フェイルオーバーのサポート

アプリケーション開発者を対象として、自動クライアント・フェイルオーバーについて、TimesTen実装のODBCでのサポート状況を説明します。

TimesTen Scaleoutの場合は、『Oracle TimesTen In-Memory Database Scaleoutユーザーズ・ガイド』クライアント接続フェイルオーバーを参照してください。TimesTen Classicの場合は、『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』自動クライアント・フェイルオーバーの使用を参照してください。

自動クライアント・フェイルオーバーについて

自動クライアント・フェイルオーバーは、TimesTen ScaleoutまたはTimesTen Classicのどちらかの高可用性シナリオで使用するために用意されています。TimesTen Classicには、2つのシナリオがあります。1つは、アクティブ・スタンバイ・ペアのレプリケーションを持ち、もう1つは汎用自動クライアント・フェイルオーバーと呼ばれます。

クライアントが接続しているデータベースまたはデータベース要素に障害が発生した場合は、代替のデータベースまたはデータベース要素にフェイルオーバー(接続転送)します。

  • TimesTen Scaleoutの場合、フェイルオーバーは、グリッド内の使用可能な要素についてTimesTenによって返されるリストからの要素に対して行われます。

  • アクティブ・スタンバイ・レプリケーションを使用するTimesTen Classicの場合、フェイルオーバーは新しいアクティブ・データベース(元のスタンバイ)に対して行われます。

  • 汎用自動クライアント・フェイルオーバーを使用するTimesTen Classicの場合、スキーマおよびデータが両方のデータベースで一貫していることを確認できるため、フェイルオーバーはクライアントodbc.iniファイルで構成されているリストからのデータベースに対して行われます。

    汎用自動フェイルオーバーの一般的なユースケースは、読取り専用キャッシュを使用する一連のデータベースで、各データベースのキャッシュ・データのセットは同じです。たとえば、読取り専用キャッシュ・グループが複数ある場合は、フェイルオーバー・サーバーのリストに含まれるすべてのTimesTen Classicデータベースに、同じ読取り専用キャッシュ・グループを作成します。クライアント接続が代替TimesTenデータベースにフェイルオーバーされると、キャッシュ操作によって(必要に応じて)Oracleデータベースからのデータが自動的にリフレッシュされるため、キャッシュ・データの一貫性が保たれます。

アプリケーションは新しいデータベースまたはデータベース要素に自動的に再接続されます。TimesTenは自動クライアント・フェイルオーバーが発生したときに、アプリケーションに警告を渡す機能を提供しているため、アプリケーションは適切な処理を行うことができます。

ノート:

  • 自動クライアント・フェイルオーバーは、クライアント/サーバー接続にのみ適用されます。ここで説明する機能は、直接接続には使用できません。

  • Oracle Clusterwareを使用する場合、自動クライアント・フェイルオーバーはOracle Clusterwareに対する補完機能ですが、この2つの機能は互いに依存しません。『Oracle TimesTen In-Memory Databaseレプリケーション・ガイド』Oracle Clusterwareを使用したアクティブ・スタンバイ・ペアの管理を参照してください。

ODBCでの自動クライアント・フェイルオーバーのサポートの特徴および機能

クライアントが接続しているデータベースまたはデータベース要素に障害が発生すると、代替のデータベースまたはデータベース要素へのフェイルオーバーが発生します。

フェイルオーバーが発生した場合、次の点に注意してください。

  • クライアントの接続は新しくなりますが、使用するODBC接続ハンドルは同じままです。ただし、ハンドル自体を除き、元の接続の状態は保持されません。アプリケーションは、新しいODBC文ハンドルおよび記述子ハンドルをオープンする必要があります。

  • フェイルオーバーのコールバック関数を登録すると(「ODBCフェイルオーバー・コールバック関数の実装と登録」を参照)、フェイルオーバー・イベントをリスニングしコールバック関数を呼び出すように、クライアント・プロセス内にフェイルオーバー・リスナー・スレッドが作成されます。

元の接続からのすべてのクライアント文ハンドルは、無効とマークされます。これらの文ハンドルに対してAPIコールを実行すると、通常は次のように、tt_errCode.hで定義されている個別のフェイルオーバー・エラー・コードがSQL_ERRORとともに返されます。

  • ネイティブ・エラー30105 (SQL状態08006)

  • ネイティブ・エラー47137

これの例外は、SQLErrorSQLFreeStmtSQLGetDiagRecおよびSQLGetDiagFieldコールの場合であり(ODBCのバージョンによって異なります)、それらは通常どおりに動作します。

また、次の点にも注意してください。

  • 元のデータベースまたはデータベース要素へのソケットはクローズされます。SQLDisconnectをコールする必要はありません。TimesTenは同等の処理を実行して、接続ハンドルをクリーン・アップし、リソースを解放します。

  • 新しいTimesTenデータベースまたはデータベース要素に接続する場合、元の接続要求からの同じ接続文字列およびDSN定義が適切なサーバー名とともに使用されます。

  • 新しい文ハンドルをオープンして必要なSQLPrepareコールを再実行するかどうかは、アプリケーション次第です。

  • フェイルオーバーがすでに発生し、クライアントが新しいデータベースまたはデータベース要素に接続されている場合は、次のようになります。

    • TimesTen Scaleoutの場合、次のフェイルオーバー・リクエストによって、元の接続時にTimesTenから返されたリストにある次の要素への接続が試行されます。

    • アクティブ・スタンバイ・レプリケーションを使用するTimesTen Classicの場合、次のフェイルオーバー・リクエストによって元のアクティブ・データベースへの再接続が試行されます。再接続に失敗した場合、タイムアウトになるまで2つのサーバーへの接続の試行が交互に行われ、この間は接続はブロックされます。

    • 汎用自動クライアント・フェイルオーバーを使用するTimesTen Classicの場合、次のフェイルオーバー・リクエストにより、クライアントのodbc.iniファイル内に構成されているリスト内の次のデータベースへの接続が試行されます。これは、「自動クライアント・フェイルオーバーの構成」で説明されているTTC_Random_Selection接続属性の設定により、次のデータベースになる場合も、リストからランダムに選択されたデータベースになる場合もあります。

    タイムアウト値は、TimesTenクライアント接続属性TTC_Timeout (デフォルトは60秒)に従います。(この属性については、『Oracle TimesTen In-Memory Databaseリファレンス』TTC_Timeoutを参照してください。)

  • フェイルオーバー接続は、事前に作成されるのではなく、必要に応じてのみ作成されます。

フェイルオーバーの間、TimesTenは登録されているユーザー定義関数に対するコールバックをオプションで行うことができます。この関数は、フェイルオーバーが発生している状況で実行する必要があるカスタム処理を行います。(「ODBCフェイルオーバー・コールバック関数の実装と登録」を参照してください。)

次に示すパブリック接続オプションは、新しい接続に伝播されます。該当する場合は、対応する一般接続属性をカッコ内に示しています。TT_REGISTER_FAILOVER_CALLBACKオプションは、コールバック関数を登録するために使用します。

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

次に示すオプションは、接続属性またはSQLSetConnectOptionコールで設定されている場合には新しい接続に伝播されますが、TimesTen組込みプロシージャまたは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)

次に示すオプションは、接続ハンドルで設定されている場合には新しい接続に伝播されます。

SQL_QUERY_TIMEOUT
TT_PREFETCH_COUNT

Oracle TimesTen In-Memory Databaseリファレンス接続属性を参照してください。

ノート:

最初のデータベース接続後の任意の時点でALTER SESSION文を発行する場合は、フェイルオーバー後にこの文を再発行する必要があります。

自動クライアント・フェイルオーバーの構成

TimesTen Classicでは、フェイルオーバーDSNは、TTC_Server2接続属性とTTC_Servern接続属性を使用して明確に構成する必要があります。

TTC_Server2TTC_Server_DSN2TTC_ServernまたはTCP_Port2を設定すると、自動クライアント・フェイルオーバーを使用することになります。アクティブ・スタンバイ・ペアのシナリオの場合は、アプリケーションでフェイルオーバー・メカニズムをサポートするために新しいスレッドが作成されます。

『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』TimesTen Classicの場合の自動クライアント・フェイルオーバーの構成、または『Oracle TimesTen In-Memory Database Scaleoutユーザーズ・ガイド』クライアント接続フェイルオーバーを参照してください。

次のTimesTen接続属性に注意してください。

  • TTC_NoReconnectOnFailover: これが1 (有効)に設定されている場合、TimesTenは自動再接続を除くすべての通常のクライアント・フェイルオーバー処理を実行するようになります。(たとえば、文ハンドルおよび接続ハンドルは無効としてマークされます。)これは、アプリケーションが自身の接続プーリングを行うか、フェイルオーバー後にデータベースへの自身の再接続を管理する場合に便利です。デフォルト値は0 (再接続)です。『Oracle TimesTen In-Memory Databaseリファレンス』TTC_NoReconnectOnFailoverも参照してください。

  • TTC_REDIRECT: これが0に設定されていて、目的のデータベースまたはデータベース要素に対する最初の接続試行が失敗した場合、エラーが返され、それ以上の接続試行は行われません。これは、その接続の後続のフェイルオーバーには影響しません。Oracle TimesTen In-Memory DatabaseリファレンスTTC_REDIRECTも参照してください。

  • TTC_Random_Selection: 汎用自動クライアント・フェイルオーバーを使用するTimesTen Classicの場合、デフォルト設定の1 (有効)では、フェイルオーバーが発生すると、クライアントが、TTC_Servern属性の設定で指定されたリストから代替サーバーをランダムに選択します。選択したサーバーにクライアントが接続できない場合、クライアントはリストされているサーバーのいずれかに正常に接続するまでリダイレクトし続けます。0を設定すると、TimesTenはTTC_Servernサーバーのリストを順番に処理します。『Oracle TimesTen In-Memory Databaseリファレンス』TTC_Random_Selectionも参照してください。

ノート:

これらをodbc.iniまたは接続文字列内で設定すると、設定がフェイルオーバー接続に適用されます。ODBC接続オプションまたはALTER SESSION属性としては設定できません。

ODBCフェイルオーバー・コールバック関数の実装と登録

フェイルオーバーの発生時に行うカスタム・アクションがある場合、TimesTenではこのようなアクションのためのユーザー定義関数にコールバックを行うことができます。

この関数は、新しいデータベースまたはデータベース要素への接続試行が開始されたときにコールされ、接続の試行が完了した後に再度コールされます。たとえば、文ハンドルを正常にリストアするために、この関数を使用できます。

関数APIの定義は次のとおりです。

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

説明:

  • hdbcは、障害が発生した接続のODBC接続ハンドルです。

  • foCtxは、アプリケーションで定義されたデータ構造体に対するポインタで、必要に応じて使用します。

  • foTypeは、フェイルオーバーのタイプです。これに対して、TimesTenでサポートされている値はTT_FO_SESSIONのみであり、セッションが再確立されます。この場合、文の再準備は行われません。

  • foEventは、サポートされている次の値で、発生したイベントを示します。

    • TT_FO_BEGIN: フェイルオーバーの開始。

    • TT_FO_ABORT: フェイルオーバーの失敗。TTC_Timeoutで指定した時間(アクティブ・スタンバイ・フェイルオーバーの最小値である60秒)の間、再試行が実行されましたが成功しませんでした。

    • TT_FO_END: フェイルオーバーの正常終了。

    • TT_FO_ERROR: フェイルオーバー接続に失敗しましたが、再試行されます。

    TT_FO_REAUTHはTimesTenクライアント・フェイルオーバーではサポートされないことに注意してください。

コールバック関数を登録するには、SQLSetConnectOptionコールを使用してTimesTenのTT_REGISTER_FAILOVER_CALLBACKオプションを設定します。指定するオプション値はCデータ型の構造体ttFailoverCallback_tへのポインタです。この構造体は、timesten.hファイル内で次のように定義されており、コールバック関数を参照します。

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

説明:

  • appHdbcはODBC接続ハンドルであり、SQLSetConnectOptionコール・シーケンスのhdbcと同じ値である必要があります。(これは、ドライバ・マネージャを使用している場合のドライバ・マネージャの実装詳細のために、データ構造体で必要になります。)

  • callbackFcnにより、コールバック関数を指定します。(NULLに設定すると、指定した接続のコールバックが取り消されます。フェイルオーバーは変わらずに発生しますが、アプリケーションが通知を受けることはありません。)

  • foCtxは、前述の関数説明のとおり、アプリケーションで定義されたデータ構造体へのポインタです。

コールバックを必要とする接続ごとに、TT_REGISTER_FAILOVER_CALLBACKを設定します。ttFailoverCallback_t構造体の値は、SQLSetConnectOptionコールが実行されるときにコピーされます。アプリケーションで構造体を保持する必要はありません。TT_REGISTER_FAILOVER_CALLBACKが接続に対して複数回設定されている場合、最後の設定が優先されます。

ノート:

  • コールバック関数はアプリケーションのメイン・スレッドに対して非同期で実行されるため、通常、アプリケーションによってポーリングされるフラグの設定などの簡単なタスクのみを実行させる必要があります。ただし、アプリケーションがマルチスレッド用に設計されている場合は、このような制限はありません。この場合は、たとえば関数でODBCコールを実行することもできますが、これは、foEventTT_FO_ENDを受け取っている場合にのみ安全に実行できます。

  • foCtx設定で指しているデータを管理するかどうかはアプリケーション次第です。

この例では次の機能を示しています。

  • グローバルに定義されるユーザー構造体タイプFOINFO、およびタイプFOINFOの構造体変数foStatus

  • フェイルオーバーが発生すると常にfoStatus構造体を更新するコールバック関数FailoverCallback()

  • 登録関数RegisterCallback()。次の処理を行います。

    • タイプttFailoverCallback_tの構造体failoverCallbackを宣言します。

    • foStatus値を初期化します。

    • 接続ハンドル、foStatusへのポインタおよびコールバック関数(FailoverCallback)で構成されるfailoverCallbackデータ値を設定します。

    • SQLSetConnectOptionコールを使用してコールバック関数を登録し、TT_REGISTER_FAILOVER_CALLBACKfailoverCallbackへのポインタとして設定します。

/* 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;
}

フェイルオーバーが発生すると、コールバック関数は次のような出力を生成します。

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

フェイルオーバー時のODBCアプリケーション・アクション

フェイルオーバーが発生した場合に実行されるアクションを示します。

この項の内容は次のとおりです。

アプリケーションのフェイルオーバーの場合の手順

アプリケーションでの操作に対する応答でエラー条件を受け取った場合は、アプリケーション・フェイルオーバーが進行中です。

エラー条件のリストは、「ODBCでの自動クライアント・フェイルオーバーのサポートの特徴および機能」を参照してください。

次のリカバリ・アクションを実行します。

  1. 接続に対してロールバックを発行します。これを行うまで、接続に対してそれ以上の処理は実行できません。
  2. 前の接続からすべてのオブジェクトをクリーンアップします。前の接続に関連付けられた状態やオブジェクトはいずれも保持されませんが、関連するAPIコールによる適切なクリーン・アップをお薦めします。
  3. TTC_NoReconnectOnFailover=0 (デフォルト)であると仮定すると、次の「フェイルオーバーの遅延および再試行の設定の実装」の項で説明しているように、短い間スリープ状態になります。TTC_NoReconnectOnFailover=1の場合は、かわりにアプリケーションを代替のデータベースまたはデータベース要素に手動で再接続する必要があります。
  4. 接続に関連するすべてのオブジェクトを再作成および再準備します。
  5. 進行中のトランザクションを最初から再起動します。

フェイルオーバーの遅延および再試行の設定の実装

自動クライアント・フェイルオーバー中に別のデータベースまたはデータベース要素に再接続する場合、時間がかかることがあります。このため、アプリケーションではすべてのリカバリ・アクションを1つのループ内に配置し、後続の各試行の前に発生する遅延を短くすることをお薦めします。このようにすると、合計試行回数が制限されます。

次の点に留意してください:

  • TimesTenでそのクライアントのフェイルオーバー・プロセスが完了する前に、アプリケーションによってリカバリ・アクションが試みられた場合は、「ODBCでの自動クライアント・フェイルオーバーのサポートの特徴および機能」でリストされている別のフェイルオーバー・エラー条件を受け取ることがあります。

  • 試行回数を制限していない場合は、クライアント・フェイルオーバー・プロセスが正常に完了しないと、アプリケーションがフリーズしたように見える可能性があります。たとえば、リカバリ・ループでの再試行遅延を100ミリ秒にして、再試行の最大回数を100に制限できます。理想的な値は、特定のアプリケーションおよび構成によって異なります。

この例では、これらのポイントをいくつか示します(一時的エラーの再試行についても示します。これについては、「一時的なエラー(ODBC)」を参照してください。)

/*
 * 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