2014年5月28日星期三

DataSnapCompatibility=true

测试中,当oracle定义一个字段为Number,不指定精度时,FireDac的Query拿到的时Precision和Scale都是38的TFmtBCD,而Data.DB.TFMTBCDField.Precision的定义又明确说了Precision是不能超过32的。

Precision can be a value from 0 through 32. Some database servers support more than 32 digits, but these can't be handled by client datasets, and so TFMTBCDField limits the value of Precision to be no greater than 32.

所以当服务器返回dataset到客户端后,客户端解析dataset后,也会的到一个Precision为32(服务器的38到变了32了)和Scale是38的TFMTBCDField,这个字段的值TBcd由于Scale是38超过Precision的32了,转换为其他类型比如字符串,在客户端会报Bcd OverFlow的。
追了一下在哪里将38变成32的,这是在客户端变的,代码在Data.SqlExpr的TCustomSQLDataSet.AddFieldDesc里,TCustomSQLDataSet是DBX框架里用来解析返回的TDBXReader得到DataSet的一个类。
  if FieldDesc.iFldType in [TDBXDataTypes.CurrencyType, TDBXDataTypes.BcdType] then
  begin
    FieldDesc.iUnits2 := Abs(FieldDesc.iUnits2);
    if FieldDesc.iUnits1 < FieldDesc.iUnits2 then   // iUnits1 indicates Oracle 'usable decimals'
      FieldDesc.iUnits1 := FieldDesc.iUnits2;
    // ftBCD supports only up to 18-4.  If Prec > 14 or Scale > 4, make FMTBcd
    if (FieldDesc.iUnits1 > (MaxBcdPrecision-4)) or (FieldDesc.iUnits2 > MaxBcdScale) or FNumericMapping then
    begin
      LType := ftFMTBcd;
      FieldDesc.iFldType := TDBXDataTypes.BcdType;
      if FieldDesc.iUnits1 > MaxFMTBcdDigits then //38 > 32,被强制指定为32了。
        FieldDesc.iUnits1 := MaxFMTBcdDigits;
    end;
  end;

解决的法子是,在服务器端设置FDConnection或者FDQuery也或者FDManager的DataSnapCompatibility为true,服务器的Query拿到这个字段,会设置Precision="32" Scale="12", 这样到了客户端,Precision为32,SignSpecialPlaces为12,就可以进行正常计算了。处理的代码在FireDAC.Phys里DoDefineDataTable函数里
              dtBCD,
              dtFmtBCD:
                begin
                  if oFmtOpts.DataSnapCompatibility and (rColInfo.FPrec > 32) then begin
                    if rColInfo.FPrec = rColInfo.FScale then
                      rColInfo.FScale := rColInfo.FPrec div 3; //38 div 3 = 12
                    rColInfo.FPrec := 32;
                  end;
                  oCol.Precision := rColInfo.FPrec;
                  oCol.Scale := rColInfo.FScale;
                end;
这里,可能会问,为啥服务器上TFMTBCDField.asString不会报Bcd OverFlow,因为再服务器上时Precision和Scale都是38,所以ok,看代码
function BcdToStr(const Bcd: TBcd; Format: TFormatSettings): string;
var
  Buf: array [0..66] of Char; //64 Nibbles + 1 sign + 1 decimal + #0
  PBuf: PChar;
  DecimalPos: Byte;
  I: Integer;
begin
  if Bcd.Precision = 0 then
    Exit('0');
  if (Bcd.Precision > MaxFMTBcdFractionSize) or  //MaxFMTBcdFractionSize为64,MaxFMTBcdDigits为32。这两个是嘛关系?
     ((Bcd.SignSpecialPlaces and $3F) > Bcd.Precision) then //这里关掉了SignSpecialPlaces的前两个bit后去和Precision比较,客户端因为38>32,所以异常了。
    OverflowError(SBcdOverflow);

...
有些乱,Data.DB.TFMTBCDField.Precision不能超过32,TBcd却可以到64。

没有评论:

发表评论