ODBC API を使用して mdb ファイルからメモ型データを取得する方法

 

高橋 理香
SQL Developer Support Eascalation Engineer

今回は最近見つかった、mdb ファイルからのデータ読み取りの問題の対応についてご紹介したいと思います。

1. 基本事項 - ODBC API によるデータの取得方法

ODBC API を使用してデータを取得する最も単純な方法として次のような関数シーケンスで実行する方法があります。

 

1) SQLExecDirect で SELECT 文を実行する。
2) SQLFetch で結果セットからレコードをフェッチする。
3) SQLGetData で取得したレコード内の列データを取得する。

 

※各種 API の詳細はリファレンスをご覧ください。
ODBC API Reference
https://msdn.microsoft.com/en-us/library/ms714562(v=VS.85).aspx

 

2. 発生しうる問題

次のような条件で、データの一部が欠ける問題が発生します。

- mdb からのデータ取得である。
- データには日本語 (DBCS) がデータに含まれている。
- データをバイナリ形式 (SQL_C_BINARY) で受け取る。
- SQLGetData で指定するバッファサイズが実際のデータ長よりも小さい。
- バッファの境界が日本語の先頭バイトの後ろに位置する。

例えば、次の文字列がデータに含まれていたとします。

 

0123456789あいうえお

 

これを非Unicode でバイナリで表現すると以下の通りです。

 

0

1

2

3

4

5

6

7

8

9

30

31

33

32

34

35

36

37

38

39

82A0

82A2

82A4

82A6

82A8

 

もしも上記データを、バッファサイズを 11 バイトに指定した SQLGetData で受け取る場合、バッファの境界が "あ" という文字の途中の 82 までとなります。しかしながら、この 82 が 00 に埋められます。さらに、次回の SQLGetData によるデータ取得では、"あ" という文字の残りの A0 から以降のデータが読み取られることが期待されますが、実際には、"い" という文字から読み取られることになります。その結果、受け取ったデータは以下の通りとなり、文字列の途中に不正な値が入ることになります。

 

0

1

2

3

4

5

6

7

8

9

.

30

31

32

33

34

35

36

37

38

39

00

82A2

82A4

82A6

82A8

 

3. 関連するテクノロジー

ODBC を使用する各種テクノロジーでこの現象が発生するパターンとして現在確認しているのは以下の通りです。

 

- C++ で ODBC API を使用するアプリケーション
- JDBC-ODBC ブリッジを使用したアプリケーション

 

Microsoft より提供しているテクノロジーで ODBC API を呼び出すものとしては、VBScript から ADO を使用する場合や .NET Framework で System.Data.Odbc を使用する場合がありますが、これらはいずれも Access メモ型のデータを SQL_C_WCHAR で受け取る実装となっているため、問題は発生しません。

また、ODBC API を使用して mdb ファイルにあるテーブルのデータを取得する場合、以下のいずれかの ODBC ドライバを使用することができますが、いずれのケースでも発生します。

 

a) Microsoft Access Driver (*.mdb)
b) Microsoft Access Driver (*.mdb, *.accdb)

 

なお、a) は 32bit 版しかない提供していないため、x86 環境、もしくは、x64 環境上の WOW でしか使うことができません。詳細については、以下で説明しています。

Jet データベース エンジンを使用するアプリケーションの開発/動作環境
https://msdn.microsoft.com/ja-jp/data/gg607262

 

4. 問題を回避する方法

発生条件の1つでも異なれば問題は発生しませんので、以下のいずれかの対応で適切にDBCS を含むデータを SQLGetData で受け取ることができます。

 

- データの受け取りを文字列形式とする。(例: SQL_C_WCHAR)
- バッファサイズ≧ データベースから取得される文字列のバイト長  となるように指定する。(上記の例ではバッファサイズ ≧ 20 バイト)

 

上記のいずれも困難である (ODBC API 呼び出し元でデータ型を固定しているために変更できない、データ長が長すぎるために大きなバッファを確保したくない) 場合には、少々手間はかかりますが、実行する SELECT 文で MID 関数などを使用して文字列を分割して取得することで、問題を回避してデータを受け取ることができます。例えば、前述の JDBC-ODBC ブリッジを使用する場合には、ODBC API 呼び出しは JDBC 関連モジュールに実装されているため、この方法にて回避する必要があります。

 

5. 参考コード

参考のために、C++ のコードで問題を再現させる例をご紹介します。事前に必要な作業は以下の通りです。

- mdb ファイル内に t1 というテーブルを作成し、2列目に "0123456789あいうえお" というデータを入れます。
- "JetTestDSN" という名前で mdb ファイルにアクセスするための ODBC データソースを作成します。

 

#include "stdafx.h"

#include <stdio.h> #include <windows.h> #include <sql.h> #include <sqlext.h> #include <odbcss.h>

int _tmain(int argc, _TCHAR* argv[]) {     SQLHENV henv = SQL_NULL_HENV;     SQLHDBC hdbc = SQL_NULL_HDBC;         SQLHSTMT hstmt = SQL_NULL_HSTMT;

    RETCODE retcode;     SQLCHAR BinaryPtr[11] = {"\0"};     SQLLEN  BinaryLenOrInd;     SQLLEN  NumBytes;        retcode = SQLAllocHandle (SQL_HANDLE_ENV, NULL, &henv);

    retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);

    retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

    // 事前に作成した ODBC データソースを使用してデータベースに接続します。     retcode = SQLConnect(hdbc, (SQLCHAR*)"JetTestDSN", SQL_NTS, (SQLCHAR*)"",SQL_NTS, (SQLCHAR*)"", SQL_NTS);

    retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);

    // クエリを実行します。     retcode = SQLExecDirect(hstmt, (SQLCHAR*)"select * from t1", SQL_NTS);

    // 結果セットからレコードを取得します。     while (SQLFetch(hstmt) != SQL_NO_DATA)     {         // レコード内の列データをバイナリ形式で受け取ります。         while (SQLGetData(hstmt, 2, SQL_C_BINARY, BinaryPtr, 11, &BinaryLenOrInd) != SQL_NO_DATA)         {             NumBytes = (BinaryLenOrInd > 11) || (BinaryLenOrInd == SQL_NO_TOTAL) ?                         11 : BinaryLenOrInd;             printf("[%d | %s]\n", NumBytes, BinaryPtr);             memset(BinaryPtr, 0, 11);         }

    }

    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);     SQLDisconnect(hdbc);     SQLFreeHandle(SQL_HANDLE_DBC, hdbc);     SQLFreeHandle(SQL_HANDLE_ENV, henv);

    return 0; }

上記を実行すると得られる結果は以下の通り、"あ" という文字が抜け落ちたものです。

[11 | 0123456789] [8 | いうえお]