FireDACでFireBirdへ接続時、入力する文字数によってエラーが発生する症状について

Abstract: 発生している症状に関する原因と解決方法について説明します。

blank

対象となるIDE製品

RAD Studio/C++Builder/Delphi XE5

blank

対象となるデータベースコンポーネント

FireDAC (9.0.1)

blank

対象となるデータベース

FireBird 1.5以降

blank

    問題

dummy

例えば、FireBirdのデータベースのキャラクタセットを”UTF8”で作成し、

以下のようなテーブルを作成します。

create table Table1(
    DATA varchar(20)
);

blank

XE4までは、FireDACでFireBirdに接続し、 varchar(20)のカラムに日本語文字を20文字登録した場合、エラーは発生しませんでしたが、

XE5では、同様にFireDACでFireBirdに接続し、 varchar(20)のカラムに日本語を20文字登録した場合、以下のようなエラーが発生します。

blank

Hide image

図1: エラー画面

blank

    原因

blank

 本件が発生する原因は以下の2点が理由です。

blank

(1)DriverIDの指定が異なる  

XE4までのFireDACは、InterBase/FireBirdどちらもDriverID=IB でしたが、 XE5からはFireBird専用のDriverIDが利用でき、DriverID=FBと指定することができるようなりました。

blank

(2)TWideStringField型のサイズがDriverIDによって異なる

データベースのキャラクタセットが”UTF8”の場合、 FireBird/InterBaseのvarcharのカラムは、TWideStringField型にマッピングされます。

XE4までは、InterBaseとFireBirdはDriverIDが共用だったため、例えば、varchar(20)のカラムは、どちらのデータベースもTWideStringField型、Size=80としてマッピングされました。

blank

しかしながら、XE5からはDriverIDがFireBirdとInterBaseで明確に分かれたため、 DriverIDの違いにより、TWideStringField型へマッピングされる際のサイズの仕様が変更になりました。

InterBaseは従来通り、varchar(20)の場合、TWideStringFieldのSize=60ですが、 FireBirdは、varchar(20)の場合、TWideStringFieldのSize=20になります。

上記の結果、DriverIDによって、マッピングされるサイズの仕様が変更されましたが、日本語文字を入力し、その文字数は、本来変更されたFireBirdの文字数で判定されるべきですが、

従来のInterBaseの文字数のままで判定している箇所があり、それによって不具合が生じました。

dummy

    解決

本件の回避策としてFireDAC.Phys.IBBase.pasファイルをご自身のプロジェクトへコピーし、以下のように修正してください

FireDAC.Phys.IBBase.pasファイルは、以下のパスに配置されています。

Windows 32ビットOSの場合

C:\Program Files\Embarcadero\RAD Studio\12.0\source\data\firedac

Windows 64ビットOSの場合

C:\Program Files (x86)\Embarcadero\RAD Studio\12.0\source\data\firedac

この修正はXE5 Update2をベースとしていますので、必ず、XE5 Update2へアップデートしてください。

2291行 
============= 
(修正前) 
iPrec, iScale: Integer; 

(修正後) 
iPrec, iScale, iLen: Integer; 

2413行 
=============== 
(修正前) 
SetupStringVariable(oParam, FParInfos[iBase + i], oVar) 
else if pInfo^.FADLen <> 0 then 
oVar.sqllen := pInfo^.FADLen; 

(修正後) 
SetupStringVariable(oParam, FParInfos[iBase + i], oVar) 
else if pInfo^.FADLen <> 0 then 
if (IBConnection.FEnv.Lib.Brand = ibFirebird) and 
((pInfo^.FDestSQLDataType = SQL_VARYING) or 
(pInfo^.FDestSQLDataType = SQL_TEXT)) then begin 
iLen := pInfo^.FADLen * LongWord(IBConnection.FEnv.Lib.GetBytesPerChar(oVar.sqlsubtype)); 
if iLen > 32765 then 
iLen := 32765; 
oVar.sqllen := iLen; 
end 
else 
oVar.sqllen := pInfo^.FADLen;