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を完全にサポートしています。
親トピック: 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_WCHAR、SQL_WVARCHARおよびSQL_WLONGVARCHARがODBCインタフェースに追加され、表内で定義された列がUnicodeで表現されるようになりました。これらの値は、SQLDescribeCol、SQLColAttribute、SQLColumnsおよびSQLProcedureColumnsへのコールから返すことも可能です。
Unicodeエンコードは、SQL列型NCHAR、NVARCHAR2およびNCLOBについてサポートされています。また、SQL列型CHARおよびVARCHAR2についても、文字セマンティクスが列定義で指定されている場合は、Unicodeエンコーディングがサポートされます。
ODBCドライバは、これらのSQL列型をサポートしており、それらをODBC SQLデータ型にマップします。
次の表に、サポートされているSQLデータ型および対応するODBC SQLデータ型を示します。
表7-1 サポートされるSQLデータ型および対応するODBC SQLデータ型
| SQLデータ型 | ODBC SQLデータ型 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
脚注1
文字セマンティクスが列定義で指定されている場合およびデータベースの文字セットがUnicodeの場合、CHARはSQL_WCHARにマップされます。
脚注2
文字セマンティクスが列定義で指定されている場合およびデータベースの文字セットがUnicodeの場合、VARCHAR2はSQL_WVARCHARにマップされます。
親トピック: Unicodeのサポート
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からローカル・コード・ページに変換します。
親トピック: 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としてデータをフェッチすることができます。
親トピック: 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"));
}親トピック: Unicodeのサポート