« 2007年9月 | トップページ | 2007年11月 »

2007年10月

2007年10月31日 (水)

V1.0.0.5リリース

細かい修正とリファクタリングを行ったDBXInspector V1.0.0.5をリリースしました。リファクタリングはリリースの度に何度か行っていたのですが、今回のリファクタリングでだいたい落ち着いた感じです。目立った機能追加は今回のリリースではありません。ということで、次のリリースからちょくちょく、機能追加を行おうと思ってます。

Dbxi1005a

2007年10月21日 (日)

そしてLOB型

で、実際にLOB型をLOBロケータ経由で取り出してみたがはまった。DB2 CLI独自の関数

  • SQLGetLength
  • SQLGetSubString

を使ってLOBを操作するのだが、LOBの実際のサイズを求めるSQLGetLength関数を呼び出すと何故かエラーが戻される。試しにサイズを求めず直接SQLGetSubString関数を呼び出しデータを取り出そうとしても同じエラーが戻される。戻されるエラーは

[IBM][CLI Driver]CLI0125E 関数のシーケンス・エラーです。SQLSTATE=HY010

である。関数の呼び出し順序がおかしいことを示すエラーである。色々調べても何がおかしいのか分からない。で、DB2 CLIのトレース機能を利用してトレースを取ってみた。トレース機能を有効にするには、DB2 CLIの初期化ファイルであるdb2cli.iniの[common]セクションにTrace=1というエントリを追加する(必要に応じてTraceFileNameなども追加)。取得できたトレースはこんな感じ。

Lobtrace_2 

述べたようにSQLGetLengthでエラー(SQL_ERROR)が戻されているのが分かる。ちなみに、SAMPLEデータベースのCLOB型のRESUME列を持つEMP_RESUME表を問い合わせている。また、上のログからも分かるように、RESUME列をSQL_C_CLOB_LOCATORとしてマップしてることも分かる。で、遊んだり再び調べたりすること数時間後、初めからそうしてればよかったのだが、DB2と共にインストールされたLOB列を読み書きするサンプルdtlob.cを見て、あれ。なんで文ハンドルを3つ用意してるんだ・・

理由はさておき、サンプルと同じように文ハンドルを3つ用意して実行したら、動いた・・修正前のプログラムでは、EMP_RESUME表をSELECTする文ハンドルをそのままSQLGetLength関数の引数に渡していたが、これがまずかったようである。新たに、新規の文ハンドルを割り当て、それを渡したら動くようになった。確かに、DB2 CLIのSQLGetLength関数に渡す文ハンドルの説明で、

「このステートメント・ハンドルは、準備済みステートメントまたはカタログ関数呼び出しに関連付けられててはいけません。」

とあったが、よく理解できていなった。確かに、シーケンスエラーだと。

ということで、LOBロケータを使ってLOBデータを取得するよう開発中のプログラムを修正した。

Readlob

RESUME列はCLOB(5120)として定義されている列で通常の文字列つまりCデータ型のSQL_C_CHARとしてマップすると、実際のデータのサイズに関わらず、データの切り捨てを防ぐためには、5120バイト以上のメモリを確保しなければいけないが、SQL_C_CLOB_LOCATORとしてマップしたことにより、取り出す時に実際のデータのサイズ分だけのメモリ確保すればよいことになる。

文字列型の憂鬱

先に日付・時刻型の事を書いたが、日付・時刻型より頻繁に使われるであろう文字列型も悩ましいのである。ODBCでは文字列を表すSQLデータ型として、

  • SQL_CHAR(SQL_WCHAR)
  • SQL_VARCHAR(SQL_WVARCHAR)
  • SQL_LONGVARCHAR(SQL_WVARCHAR)

が用意されている。これらに対応するアプリケーション側のCデータ型としてSQL_C_CHARがある。日付・時刻型と違い専用の構造体は用意されておらず、CやC++で文字列を扱う時と同様に、char *として扱う。括弧内はUnicode文字バージョンのSQLデータ型である。

名前を見れば分かるように、SQL_CHARは固定長の文字列、SQL_VARCHARは可変長の文字列、SQL_LONGVARCHARはより長い可変長の文字列を表すSQLデータ型で、ドライバ依存だが、DB2 CLIでは、DB2のCHAR型はSQL_CHAR、VARCHAR型はSQL_VARCHAR、LONG VARCHAR型はSQL_LONGVARCHAR、CLOB型はSQL_CLOBとして報告される。データベースのデータ型をODBCのどのSQLデータ型にマップするかは、ドライバ依存で、バイト数もしくは文字数がN以上ならSQL_LONGVARCHARにマップしなければいけないとかそのような規則はMicrosoftのODBCのヘルプを見る限りなさそうである。が、慣習的にLOB型などの非常に大きなサイズを許可するデータ型は、ほとんどのODBCドライバはLONGが付くSQL_LONGVARCHARなどにマップするようである。もしくは、DB2 CLIのように独自のSQLデータ型に(LOBの場合、SQL_CLOB、SQL_BLOB、SQL_DBLOB)。

悩ましいのここからで、列のデータを取り出すのに割り当てるメモリのサイズをどうすればいいかで頭を痛める。

例えば、可変長のデータ型に限ったことではないが、SQL_VARCHARのSQLデータ型として報告される列を取り出す場合を考える。SQL文をユーザーが発行できるツールなど汎用的なアプリの場合、事前に実際の列データのサイズを知ることはできないので、データの切り捨てが発生しないよう普通は列の情報を問い合わせたときに返される最大サイズ分のメモリ領域を割り当てる。想像通り、返される最大サイズが小さければいいが、2GBとか巨大なサイズが返されると・・しかも、可変長のデータ型の場合、最大サイズが巨大でも、実際に格納されている列データのサイズが小さいと、メモリの無駄である。

と色々悩ましいのである。ちなみに、DB2のCHAR、VARCHARやLONG VARCHAR型は定義できる最大サイズが約32KBなので、返される最大サイズ分のメモリを一度に割り当てて列データを取り出しても問題ないとは思う。DB2で問題なのは最大サイズが約2GBのCLOB型などのLOB型のデータ型である。

これらのDB2のデータ型に対して、DB2 CLIはLOBデータを操作するためにLOBロケータというものを用意しアプリケーション側から効率的にLOBデータにアクセスできるようになっている(他のデータベースにもあるが)。LOBロケータとしてマップするためのDB2 CLI独自のCデータ型として

  • SQL_C_CLOB_LOCATOR
  • SQL_C_BLOB_LOCATOR
  • SQL_C_DBLOB_LOCATOR

が用意され、これらのC言語における定義はSQLINTEGERつまり4バイトの整数値となっている。

つまり、アプリケーション側が見れば、LOBロケータはそれらを識別する単なる4バイトの整数値として扱え、LOBデータを取り出す時に、とりあえず、LOBロケータを格納する4バイトのメモリ領域だけ割り当て、そこにLOBロケータを取り出し、実際にそのLOBロケータによって参照されるLOBデータを取り出す必要がある時に、最大のサイズではなく実際のLOBデータのサイズだけを返すDB2 CLI専用の関数を呼び、必要最小限のメモリだけを割り当てて取り出すことができる(もちろん、分割して取り出すこともできる)。実際のLOBデータが巨大だったら、まぁ、どうにかしないといけないが・・・・

また、ODBCには大きなサイズのデータを扱うための仕組みが用意され、それを使うこともできるが、専用の関数を使った方がプログラム的には楽である(と思う・・)。

2007年10月20日 (土)

日付・時刻型の憂鬱

DB2 CLIつまりODBCでは、日付・時刻に関連するデータベース側のデータ型(ODBCではSQLデータ型と呼ぶ)として、

  • SQL_TYPE_DATE
  • SQL_TYPE_TIME
  • SQL_TYPE_TIMESTAMP
  • SQL_INTERVALで始まる間隔型

が用意されている。

SQL_TYPE_DATEは年、月、日を要素として持つ日付を表すSQLデータ型で、データをアプリケーション側のデータ型(ODBCでは、Cデータ型と呼ぶ)にマップする時に、SQL_C_TYPE_DATEという、Cデータ型としてマップすることができる。SQL_C_TYPE_DATEというCデータ型は、以下のように定義されている。

Odbcdate

上記の構造体にマップすることができる。また、文字列(char *)を表すCデータ型であるSQL_C_CHARにもマップすることもできる。上記のSQL_C_TYPE_DATEにマップする場合、32ビット環境では、2バイト*3フィールド=6バイトのメモリ領域があればいいが、文字列としてSQL_C_CHARにマップする場合、切り捨てが発生しないよう日付を文字列として表現できる十分なメモリを確保する必要がある(日付の区切り文字等を考慮しないといけない)。

SQL_TYPE_TIMEは、時、分、秒を要素として持つ時刻を表すSQLデータ型でSQL_TYPE_DATE同様、Cデータ型として、SQL_C_TYPE_TIMEというデータ型が用意されている。

Odbctime_2

ちなみに、秒は、小数部を持つことができる。

以下同様、SQL_TYPE_TIMESAMPSQL_C_TYPE_TIMESTAMPというCデータ型が用意されている。

Odbctimestamp

で、前回書いたように、DB2のデータ型であるTIMEはSQL_TYPE_TIME、DATEはSQL_TYPE_DATE、TIMESTAMPはSQL_TYPE_TIMESTAMPとして報告されるのである。

悩ましいのはここからで、自分が使ってるデータベースのフレームワークでは、さらに、フレームワークのデータ型にマップさせなければいけないのだが、例えば、フレームワークに用意されている日付型の範囲は0001/01/01~9999/12/31までで、他方、上記のDATE_STRUCT構造体のyearフィールドの型を見ると、SQLSMALLINTで紀元前も許可するではないか・・と、重箱の隅をつつくような事で悩んでいるのである(時刻型の秒の小数点以下の桁数も同様)。幸い、DB2のDATE型は0001年から9999年までであるが・・・(OracleのDATE型は紀元前も許可する)。で、日付・時刻の精度について色々調べたら、こんなブログを発見。では、日付型をフレームワークの文字列型にマップしてはどうか?と考えると、大半の他のアプリケーションがそうであるように、Windowsのロケールの設定から日付や時刻の区切り文字を取得して、文字列を組み立てたくなる(フレームワークの日付・時刻型はそうなっている)。更に本格的にやるとなると、表示後、Windowsのロケールの設定が変更されたら、それを感知し文字列を再組み立てしたくもなる(フレームワークはそうしてる)。

とまぁ、なんて賢いフレームワークなんだ!!と。

ちなみに、DB2には、2つの日付・時刻の差を表す間隔型はないが、ODBCではSQL_INTERVALという接頭辞で始まるSQLデータ型が用意されている。対応するCデータ型もSQL_C_INTERVALという接頭辞が付いており、構造体が用意されている。あいにく、自分の使っているフレームワークではそのような型は用意されていないので、文字列として扱う必要がある。

と色々、汎用的なアプリを作ろうとなると、データベースとアプリケーション側でのデータ型の表現能力の違いで色々悩ましいのである。

2007年10月19日 (金)

データ型のマップ

息抜きがてらに、色々な事をやっていて、少しづつ、DB2 CLI/ODBCを使った接続アプリのサンプルを作っていたのだが、ある程度動くようになってきた。こんな感じに

Db2emp_2

上のテキストボックスに入力したSQL文の実行結果が下のグリッドに表示されるという、基本的なプログラムであるが・・グリッド上でデータを編集して、反映ボタンを押すと、変更内容がサーバー上のデータベースに反映される。ちなみに、上の画像はDB2にあらかじめ用意されているSAMPLEデータベースのEMPLOYEEテーブルの内容である。プログラムの作成中に一番悩んだのは、データ型の変換・マップの仕方である。通常、DB2に限らずデータベースにアプリケーションからアクセスする場合、データベース側のデータ型とアプリケーション開発環境で用意されているデータ型は1対1に対応しないので、何かしらの変換が必要になる。例えば、DB2で使用できる組み込みデータ型には以下のものがある。

  • SMALLINT(SQL_SMALLINT)
  • INTEGER(SQL_INTEGER)
  • BIGINT(SQL_BIGINT)
  • DECIMAL/NUMERIC(SQL_DECIMAL)
  • REAL(SQL_FLOAT)
  • DOUBLE/FLOAT(SQL_DOUBLE)
  • CHAR(SQL_CHAR)
  • VARCHAR(SQL_VARCHAR)
  • LONG VARCHAR(SQL_LONGVARCHAR)
  • CLOB(SQL_CLOB*)
  • GRAPHIC
  • VARGRAPHIC
  • LONG VARGRAPHIC
  • DBCLOB
  • BLOB(SQL_BLOB*)
  • DATE(SQL_TYPE_DATE)
  • TIME(SQL_TYPE_TIME)
  • TIMESTAMP(SQL_TYPE_TIMESTAMP)

上記の括弧()内はDB2 CLI/ODBCでのデータ型である。例えば、DB2のINTEGER型の列は、DB2 CLI/ODBCを使って、その列の型を問い合わせるとODBCの標準データ型であるSQL_INTEGERとして報告される。また、LOB型のSQL_CLOB,SQL_BLOBはODBCの標準データ型ではなく、DB2独自のデータ型なので*(アスタリスク)をつけた。また、DB2のLOB型はDB2 CLIの設定ファイルであるdb2cli.iniにLongDataCompat=1というエントリを追加すると、LOB型はODBCの標準データ型であるSQL_LONGVARCHARやSQL_LONGVARBINARYとして報告されるようになる。

で、問題は、これらのデータベース側のデータ型のデータをアプリケーションが取り出すときに、どうアプリケーション側のデータ型にマップするかで、頭を悩まされる。例えば、DB2のINTEGER型(SQL_INTEGER)は4バイトの整数を表す型で、今時の開発環境では組み込みのデータ型として4バイトの整数を表す型、C言語で言えば、処理系に依存するかもしれないが一般的にlong型が用意されているので、そのまま取り出せる。また、4バイトの整数としてではなく、文字列(char *)として取り出せることもできるが、その場合、4バイトの整数を文字列として表現し格納できるだけの十分な領域を確保しないと、切り捨てが発生する(符号付き4バイト整数の場合、-21億から+21億の値を表現できるので、符号も含めて最低12バイト。小数部を持つデータ型の場合、小数点も考慮にいれなければいけない)。ちなみに、これらの変換はサポートされていればドライバまたはドライバマネージャがやってくれる。

とまぁ、INTEGER型ならいいが、DECIMAL型とか巨大な数を扱う場合、文字列にマップしないと切り捨られる可能性があるので、色々、効率を考慮すると面倒なのである。すべての数値型を文字列にマップすればあまり考え込む必要はないが・・(マイクロソフトのODBCのヘルプにデフォルトのデータ型のマップの表があるので、それを参考にすれば、あまり悩む必要はないかもしれない。)

ちなみに、作っているサンプルプログラムはDB2のLOB型(SQL_CLOB,SQL_BLOB)やグラッフィクストリングにはまだ対応していない。上記の表で、グラフィックストリング型がODBCのどのデータ型にマップされているかはまだ調べていない(SQL_WCHAR?SQL_BINARAY?)。

2007年10月14日 (日)

受付開始

まさか、休日に更新されるとは思っていなかったのですが、Vectorの方から、シェアレジ作品公開のメールが来ました。シェアレジで購入できるようになりました。最新版のアーカイブに含まれるreadme.txt内のリンクもしくは、ここから現在辿れます。DBXInspectorのホームページも後で更新しておきます。

Vectorから来たメールに書いてありますが、

「ライブラリ」コーナーでの上記作品の紹介部分にレジサービスへのリンクが表示されるのは、このご案内から、数日ないし1週間ほど後になります。

となります。

2007年10月13日 (土)

遅れてます。

うーん。先週にベクターへライブラリの差し替え依頼とレジ作品登録を依頼したのですが、遅れてます。ステータスがずっと変わらなかったので、昨日、ベクターの方へメールしたら、ベクターで最新版への作品の差し替えは完了した模様でメールが来ました。作品区分もシェアになりました。でも、レジ作品の登録のステータスが変わってません。週明けにでももう一度メールして督促してみます。

2007年10月 3日 (水)

正式リリース

DBXInspectorを正式リリースしました。本体価格は4000円です。ライセンスキーの購入ははベクター・シェアレジサービスでできるようになります(現在、シェアレジの登録依頼と作品の入れ替えを申請中なので、来週の初頭ぐらいには、シェアレジで購入できるようになると思います)。最新版のダウンロードはDBXInspectorホームページの方からお願いします。ライセンスを購入せずに使用する場合は、フリーモードで動作し、期間制限はありませんが、発行できるSQL文の長さとSQL履歴数に今まで通り制限があります。

前回のリリースからの変更履歴はhistory.txtを見て下さい。主に、リファクタリングと細かな修正を行っています。

« 2007年9月 | トップページ | 2007年11月 »

自作ソフトウェア

無料ブログはココログ

メモ