7 Unicodeのサポート

この章では、Oracle Database ODBCドライバでのUnicodeサポートについて説明します。

トピック:

7.1 ODBC環境内でのUnicodeのサポート

MicrosoftまたはunixODBC ODBCドライバ・マネージャ(ドライバ・マネージャ)は、Unicodeをサポートしているかどうかに関係なく、すべてのODBCドライバをUnicodeに準拠しているように見せます。これによって、ODBCアプリケーションは、基礎となるODBCドライバのUnicode機能に関係なく記述できます。

Driver ManagerがANSI ODBCドライバに対するUnicodeサポートをエミュレートできる範囲は、Unicodeデータとローカル・コード・ページ間で可能な変換内容によって制限されます。Driver ManagerがデータをUnicodeからローカル・コード・ページに変換する際、データが失われる場合があります。基礎となるODBCドライバがUnicodeをサポートしていないかぎり、Unicodeの完全なサポートは不可能です。Oracle Database ODBCドライバはUnicodeを完全にサポートしています。

7.2 ODBC APIでのUnicodeのサポート

ODBC APIは、"W"と"A"という決まった接尾辞によって、UnicodeおよびANSIエントリ・ポイントの両方をサポートします。ODBCアプリケーション開発者は、接尾辞を指定してエントリ・ポイントを明示的にコールする必要はありません。UNICODEおよび_UNICODEプリプロセッサ定義を使用してコンパイルされたODBCアプリケーションによって、適切なコールが生成されます。たとえば、SQLPrepareコールはSQLPrepareWとしてコンパイルされます。

Cデータ型のSQL_C_WCHARがODBCインタフェースに追加されたため、アプリケーションでは、入力パラメータをUnicodeとしてエンコードするように指定するか、またはUnicodeとして返された列データを要求できます。マクロのSQL_C_TCHARは、UnicodeおよびANSIの両方で作成する必要があるアプリケーションで有効です。SQL_C_TCHARマクロは、Unicodeアプリケーションの場合はSQL_C_WCHARとして、ANSIアプリケーションの場合はSQL_C_CHARとしてコンパイルされます。

SQLデータ型のSQL_WCHARSQL_WVARCHARおよびSQL_WLONGVARCHARがODBCインタフェースに追加され、表内で定義された列がUnicodeで表現されるようになりました。これらの値は、SQLDescribeColSQLColAttributeSQLColumnsおよびSQLProcedureColumnsへのコールから返すことも可能です。

Unicodeエンコードは、SQL列型NCHARNVARCHAR2およびNCLOBについてサポートされています。また、SQL列型CHARおよびVARCHAR2についても、文字セマンティクスが列定義で指定されている場合は、Unicodeエンコーディングがサポートされます。

ODBCドライバは、これらのSQL列型をサポートしており、それらをODBC SQLデータ型にマップします。

次の表に、サポートされているSQLデータ型および対応するODBC SQLデータ型を示します。

表7-1 サポートされるSQLデータ型および対応するODBC SQLデータ型

SQLデータ型 ODBC SQLデータ型

CHAR

SQL_CHARまたはSQL_WCHAR 脚注1

VARCHAR2

SQL_VARCHARまたはSQL_WVARCHAR 脚注2

NCHAR

SQL_WCHAR

NVARCHAR2

SQL_WVARCHAR

NCLOB

SQL_WLONGVARCHAR

脚注1

文字セマンティクスが列定義で指定されている場合およびデータベースの文字セットがUnicodeの場合、CHARSQL_WCHARにマップされます。

脚注2

文字セマンティクスが列定義で指定されている場合およびデータベースの文字セットがUnicodeの場合、VARCHAR2SQL_WVARCHARにマップされます。

7.3 ドライバ・マネージャのUnicode機能

ドライバ・マネージャ(DM)は、ODBCドライバがUnicodeをサポートしていないことを検出すると、次のことを実行します。

  • DMは、ANSI ODBCドライバをコールする前にUnicode用関数コールをANSI用関数コールに変換します。文字列引数はUnicodeからローカル・コード・ページに変換されます。たとえば、SQLPrepareWコールはSQLPrepareコールに変換されます。SQL文パラメータの文字列はUnicodeからローカル・コード・ページに変換されます。

  • DMは、文字データのリターン・パラメータをローカル・コード・ページからUnicodeに変換します。たとえば、SQLColAttributeを使用して列名を戻す場合です。

  • DMは、SQL_C_WCHARでバインドされた列に対して、データをローカル・コード・ページからUnicodeに変換します。

  • DMは、SQL_C_WCHARでバインドされた入力パラメータに対して、データをUnicodeからローカル・コード・ページに変換します。

7.4 SQLGetDataのパフォーマンス

SQLGetData関数を使用すると、ODBCアプリケーションはデータ型を指定して、データのフェッチ後に列を取得できます。OCIでは、Oracle Database ODBCドライバがフェッチ前にデータ型を指定する必要があります。この場合、Oracle Database ODBCドライバは、データベース内で定義されている列のデータ型に関する情報を使用して、OCI経由で列をフェッチする最適な方法を決定します。

文字データを含む列がSQLBindColによってバインドされていない場合、Oracle Database ODBCドライバは、その列をUnicodeとしてフェッチするか、ローカル・コード・ページとしてフェッチするかを判断する必要があります。ドライバは、デフォルトとして列をUnicodeで受け取ることが可能ですが、結果として不要な変換を2回行うことになります。たとえば、データベースでデータがANSIでエンコードされている場合、Oracle Database ODBCドライバにデータを送る際にANSIからUnicodeに変換されます。ODBCアプリケーションがSQL_C_CHARとしてデータを要求すると、元のエンコーディングに戻すために、さらに変換が行われることになります。

データのフェッチでは、Oracleクライアントのデフォルトのエンコーディングが使用されます。ODBCアプリケーションでは、WCHARデータ型として列またはパラメータをバインドすることにより、このデフォルトを上書きして、Unicodeとしてデータをフェッチすることができます。

7.5 Unicodeのサンプル

Oracle Database ODBCドライバ自体がTCHARマクロを使用して実装されているため、ODBCアプリケーション・プログラムでは、TCHARを使用してドライバを活用することをお薦めします。

次のプログラムではTCHARの使用方法を示します。TCHARはUNICODEおよび_UNICODEを指定してコンパイルする場合は、WCHARデータ型になります。

例1 データベースへの接続

SQLConnectに対してUnicodeリテラルを使用する以外、他との違いはありません。

SQLHENV envHnd;
SQLHDBC conHnd;
SQLHSTMT stmtHnd;
RETCODE rc;

rc = SQL_SUCCESS;

// ENV is allocated
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &envHnd);
// Connection Handle is allocated
rc = SQLAllocHandle(SQL_HANDLE_DBC, envHnd, &conHnd);
rc = SQLConnect(conHnd, _T("stpc19"), SQL_NTS, _T("scott"), SQL_NTS, _T("tiger"),
 SQL_NTS);
.
.
.
if (conHnd)
{
  SQLDisconnect(conHnd);
  SQLFreeHandle(SQL_HANDLE_DBC, conHnd);
}
if (envHnd)
  SQLFreeHandle(SQL_HANDLE_ENV, envHnd);

例2 単純な取出し

次の例では、従業員名と職種をEMP表から取り出します。すべてのODBC関数にTCHAR準拠のデータを指定する必要があることを除いて、ANSIの場合と違いはありません。Unicodeアプリケーションの場合は、SQLBindColをコールするときにバッファの長さをBYTE長で指定する必要があります(たとえば、sizeof(ename))。

/*
** Execute SQL, bind columns, and Fetch.
** Procedure:
**
** SQLExecDirect
** SQLBindCol
** SQLFetch
**
 */
static SQLTCHAR *sqlStmt = _T("SELECT ename, job FROM emp");
SQLTCHAR ename[50];
SQLTCHAR job[50];
SQLINTEGER enamelen, joblen;
 
_tprintf(_T("Retrieve ENAME and JOB using SQLBindCol 1.../n[%s]/n"), sqlStmt);
 
/* Step 1: Prepare and Execute */
rc = SQLExecDirect(stmtHnd, sqlStmt, SQL_NTS); /* select */
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 2: Bind Columns */
rc = SQLBindCol(stmtHnd, 1, SQL_C_TCHAR, ename, sizeof(ename), &enamelen);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
rc = SQLBindCol(stmtHnd, 2, SQL_C_TCHAR, job, sizeof(job), &joblen);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
do
{
  /* Step 3: Fetch Data */
  rc = SQLFetch(stmtHnd);
  if (rc == SQL_NO_DATA)
    break;
  checkSQLErr(envHnd, conHnd, stmtHnd, rc);
  _tprintf(_T("ENAME = %s, JOB = %s/n"), ename, job);
} while (1);
_tprintf(_T("Finished Retrieval/n/n"));

例3 SQLGetDataを使用したデータ取出し(フェッチ後のバインド)

この例では、SQLGetDataの使用方法を示します。ODBCプログラミングに精通していない場合は、OCIプログラムの場合と異なり、SQLGetDataを使用することで、データをバインドする前にフェッチすることが可能です。Unicode固有の事項に関しては、ANSIアプリケーションとの違いはありません。

/*
** Execute SQL, bind columns, and Fetch.
** Procedure:
**
** SQLExecDirect
** SQLFetch
** SQLGetData
 */
static SQLTCHAR *sqlStmt = _T("SELECT ename,job FROM emp"); // same as Case 1.
SQLTCHAR ename[50];
SQLTCHAR job[50];
 
_tprintf(_T("Retrieve ENAME and JOB using SQLGetData.../n[%s]/n"), sqlStmt);
if (rc != SQL_SUCCESS)
{
  _tprintf(_T("Failed to allocate STMT/n"));
  goto exit2;
}
 
/* Step 1: Prepare and Execute */
rc = SQLExecDirect(stmtHnd, sqlStmt, SQL_NTS); // select
checkSQLErr(envHnd, conHnd, stmtHnd, rc);

do
{
  /* Step 2: Fetch */
  rc = SQLFetch(stmtHnd);
  if (rc == SQL_NO_DATA)
    break;

  checkSQLErr(envHnd, conHnd, stmtHnd, rc);

  /* Step 3: GetData */
  rc = SQLGetData(stmtHnd, 1, SQL_C_TCHAR, (SQLPOINTER)ename, sizeof(ename), NULL);
  checkSQLErr(envHnd, conHnd, stmtHnd, rc);
  
  rc = SQLGetData(stmtHnd, 2, SQL_C_TCHAR, (SQLPOINTER)job, sizeof(job), NULL);
  checkSQLErr(envHnd, conHnd, stmtHnd, rc);

  _tprintf(_T("ENAME = %s, JOB = %s/n"), ename, job);

} while (1);

_tprintf(_T("Finished Retrieval/n/n"));

例4 単純な更新

この例では、データの更新方法を示します。同様に、SQLBindParameterに対するデータ長は、Unicodeアプリケーションの場合でもBYTE長で指定します。

/
*
** Execute SQL, bind columns, and Fetch.
** Procedure:
**
** SQLPrepare
** SQLBindParameter
** SQLExecute
*/

static SQLTCHAR *sqlStmt = _T("INSERT INTO emp(empno,ename,job) VALUES(?,?,?)");
static SQLTCHAR *empno = _T("9876"); // Emp No
static SQLTCHAR *ename = _T("ORACLE"); // Name
static SQLTCHAR *job = _T("PRESIDENT"); // Job
 
_tprintf(_T("Insert User ORACLE using SQLBindParameter.../n[%s]/n"), sqlStmt);
 
/* Step 1: Prepare */

rc = SQLPrepare(stmtHnd, sqlStmt, SQL_NTS);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 2: Bind Parameter */

rc = SQLBindParameter(stmtHnd, 1, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_DECIMAL,4, 0, (SQLPOINTER)empno, 0, NULL);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);

rc = SQLBindParameter(stmtHnd, 2, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_CHAR, lstrlen(ename)*sizeof(TCHAR), 0, (SQLPOINTER)ename, lstrlen(ename)*sizeof(TCHAR), NULL);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
rc = SQLBindParameter(stmtHnd, 3, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_CHAR, lstrlen(job)*sizeof(TCHAR), 0, (SQLPOINTER)job, lstrlen(job)*sizeof(TCHAR), NULL);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 3: Execute */

rc = SQLExecute(stmtHnd);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);

例5 LONGデータ(CLOB)の更新と取出し

この例は、OracleのCLOBのような大きなデータの更新と取出しをする、最も複雑な場合の例です。データ長はBYTE長である必要があるため、BYTE長を導出するにはlstrlen(TCHAR data)*sizeof(TCHAR)が必要です。

/*
** Execute SQL, bind columns, and Fetch.
** Procedure:
**
** SQLPrepare
** SQLBindParameter
** SQLExecute
** SQLParamData
** SQLPutData
**
** SQLExecDirect
** SQLFetch
** SQLGetData
 */

static SQLTCHAR *sqlStmt1 = _T("INSERT INTO clobtbl(clob1) VALUES(?)");
static SQLTCHAR *sqlStmt2 = _T("SELECT clob1 FROM clobtbl");
SQLTCHAR clobdata[1001];
SQLTCHAR resultdata[1001];
SQLINTEGER ind = SQL_DATA_AT_EXEC;
SQLTCHAR *bufp;
SQLTCHAR ch;
int clobdatalen, chunksize, dtsize, retchklen, i, len;
 
_tprintf(_T("Insert CLOB1 using SQLPutData.../n[%s]/n"), sqlStmt1);
 
/* Set CLOB Data *

for (i=0, ch=_T('A'); i< sizeof(clobdata)/sizeof(SQLTCHAR); ++i, ++ch)
{
  if (ch > _T('Z'))
  ch = _T('A');
  clobdata[i] = ch;
}

clobdata[sizeof(clobdata)/sizeof(SQLTCHAR)-1] = _T('/0');
clobdatalen = lstrlen(clobdata);
chunksize = clobdatalen / 7;
 
/* Step 1: Prepare */
rc = SQLPrepare(stmtHnd, sqlStmt1, SQL_NTS);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 2: Bind Parameter with SQL_DATA_AT_EXEC */
rc = SQLBindParameter(stmtHnd, 1, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_LONGVARCHAR, clobdatalen*sizeof(TCHAR), 0, (SQLPOINTER)clobdata, clobdatalen*sizeof(TCHAR), &ind);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 3: Execute */
rc = SQLExecute(stmtHnd);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
sdhamoth: Continuation:

 
/* Step 4: ParamData (initiation) */
rc = SQLParamData(stmtHnd, (SQLPOINTER*)&bufp);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
for (dtsize=0, bufp = clobdata; dtsize < clobdatalen; dtsize += chunksize, bufp += chunksize)
{
  if (dtsize+chunksize<clobdatalen)
    len = chunksize;
  else
    len = clobdatalen-dtsize;
 
  /* Step 5: PutData */
  rc = SQLPutData(stmtHnd, (SQLPOINTER)bufp, len*sizeof(TCHAR));
  checkSQLErr(envHnd, conHnd, stmtHnd, rc);
}
 
/* Step 6: ParamData (termination) */
rc = SQLParamData(stmtHnd, (SQLPOINTER*)&bufp);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
rc = SQLFreeStmt(stmtHnd, SQL_CLOSE);
_tprintf(_T("Finished Update/n/n"));

rc = SQLAllocStmt(conHnd, &stmtHnd);
if (rc != SQL_SUCCESS)
{
  _tprintf(_T("Failed to allocate STMT/n"));
  goto exit2;
}
 
/* Clear Result Data */
memset(resultdata, 0, sizeof(resultdata));
chunksize = clobdatalen / 15; /* 15 times to put */
 
/* Step 1: Prepare */
rc = SQLExecDirect(stmtHnd, sqlStmt2, SQL_NTS); /* select */
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
/* Step 2: Fetch */
rc = SQLFetch(stmtHnd);
checkSQLErr(envHnd, conHnd, stmtHnd, rc);
 
for(dtsize=0, bufp = resultdata; dtsize < sizeof(resultdata)/sizeof(TCHAR) && rc != SQL_NO_DATA; dtsize += chunksize-1, bufp += chunksize-1)
{
  if (dtsize+chunksize<sizeof(resultdata)/sizeof(TCHAR))
    len = chunksize;
  else
    len = sizeof(resultdata)/sizeof(TCHAR)-dtsize;
 
  /* Step 3: GetData */
  rc = SQLGetData(stmtHnd, 1, SQL_C_TCHAR, (SQLPOINTER)bufp, len*sizeof(TCHAR), &retchklen);
}

if (!_tcscmp(resultdata, clobdata))
{
  _tprintf(_T("Succeeded!!/n/n"));
}
else
{
  _tprintf(_T("Failed!!/n/n"));
}