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のサポート