Delphi / FireDAC StrsTrim2Len对ParamByName没有影响

时间:2017-09-18 15:55:48

标签: delphi firebird2.5 firedac

使用Delphi XE7和Tokyo与Firebird 2.5我得出的结论是StrsTrim2Len在使用TFDQueryParamByName进行更新/插入时没有任何影响,这会产生超大的字符串提出异常。

除了截断代码中的所有字符串之外还有其他方法,如:

ParamByName('Field1').AsString := SomeVar.SubString(0, 50);

还需要跟踪字段长度吗?

来源和形式是:

unit Unit3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  System.StrUtils,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf,
  FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.FB,
  FireDAC.Phys.FBDef, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt,
  Vcl.StdCtrls, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FireDAC.Comp.UI, FireDAC.Phys.IBBase;

type
  TForm3 = class(TForm)
    FDConnection1: TFDConnection;
    FDPhysFBDriverLink1: TFDPhysFBDriverLink;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    FDQuery1: TFDQuery;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
begin
  FDConnection1.Open;

  FDQuery1.FormatOptions.StrsTrim2Len := True;

  FDQuery1.SQL.Text := 'INSERT INTO MyTable (ID, MyField) VALUES (:ID, :MyField)';
  FDQuery1.ParamByName('ID').AsInteger := 1;
  FDQuery1.ParamByName('MyField').AsString := DupeString('0', 21); { ← field is 20 chars }
  FDQuery1.ExecSQL;

  FDQuery1.SQL.Text := 'SELECT MyField FROM MyTable WHERE ID = 1';
  FDQuery1.Open;

  Assert(Length(FDQuery1.FieldByName('MyField').AsString) = 20); { ← trimmed to 20 chars? }

  FDConnection1.Close;
end;

end.

相应的.dfm文件:

object Form3: TForm3
  Left = 0
  Top = 0
  Caption = 'Form3'
  ClientHeight = 294
  ClientWidth = 161
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 40
    Top = 16
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object FDConnection1: TFDConnection
    Params.Strings = (
      'Database=MyUTF8Db'
      'User_Name=Sysdba'
      'Password='
      'Server=127.0.0.1'
      'CharacterSet=UTF8'
      'DriverID=FB')
    FormatOptions.AssignedValues = [fvStrsTrim2Len]
    FormatOptions.StrsTrim2Len = True
    Left = 48
    Top = 48
  end
  object FDPhysFBDriverLink1: TFDPhysFBDriverLink
    Left = 48
    Top = 104
  end
  object FDGUIxWaitCursor1: TFDGUIxWaitCursor
    Provider = 'Forms'
    ScreenCursor = gcrHourGlass
    Left = 48
    Top = 160
  end
  object FDQuery1: TFDQuery
    Connection = FDConnection1
    Left = 48
    Top = 216
  end
end

1 个答案:

答案 0 :(得分:0)

是否需要在启用StrsTrim2Len的情况下手动修剪参数值?

不,当您启用StrsTrim2Len选项时,即使已分配的参数值也应修剪为绑定字段的限制。因此,对于字符串字段参数,直接字符串赋值应修剪长度值,恕我直言:

ParamByName('MyFieldUTF8').AsWideString := 'Value to be encoded by FireDAC';

为什么恕我直言?因为在这个特殊情况下(Delphi东京,Firebird中的UTF-8字段),此选项的实现类似于 DataTrim2Size 而不是它的读取方式,我不确定是否有某些原因让它离开背后。 Firebird中的字符串字段受字符串长度限制的约束,而不是定义时的存储数据大小限制(据我所知)。

自动字符串参数值修整有什么问题呢?

FireDAC,现在为Firebird驱动程序实现,只有当编码参数值的数据大小超过存储时,修剪参数数据缓冲区才能获得UTF-8字段参数字段的数据大小(启用StrsTrim2Len选项时)。因此,它基于数据大小而不是字符串长度

参数数据大小(如果未指定)似乎是从 RDB $ FIELDS 系统表的 RDB $ FIELD_LENGTH 元字段获得的(无法确认,但似乎sqllen成员由该字段值填充,来自快速Firebird代码浏览)。但它现在并不那么重要。

问题是这段代码和StrsTrim2Len选项在这里实际应该做什么的类比(它是 TIBVariable.SetData 方法实现,UTF-8分支;添加了注释由我):

{ this encodes the value to UTF-8 and returns number of bytes written to the buffer }
iByteLen := FVars.Statement.Database.Encoder.Encode(ApData, ALen, pUTF8, ecUTF16);
{ DataSize here equals to the RDB$FIELD_LENGTH, so let's try a calculation for field
  let's say VARCHAR(20), and to the parameter binded to it assign e.g. 21 chars long
  string consisting from ANSI chars:

  iByteLen → 21 ANSI chars occupies 21 bytes
  DataSize → XSQLVAR.sqllen → RDB$FIELD_LENGTH → 80 (20 chars * max UTF-8 char size?)

  Now, is 21 > 80? No? No trimming then. }
if iByteLen > DataSize then
  if FVars.Statement.StrsTrim2Len then
    iByteLen := DataSize
  else
    ErrorDataTooLarge(DataSize, iByteLen);

正如您可能猜到的那样,实际上会对参数值缓冲区进行修剪,但编码后的字符串太小,因此跳过了修剪。你也可以预测,如果你使用这样的参数,例如为了插入一个值,你最终会得到引擎异常,因为在验证约束时引擎使用插入的字符串值长度而不是数据大小。

我在这里找不到使用数据大小的实际意义。好吧,您永远不会溢出存储数据大小,但另一方面,您可以轻松超过该字段的长度。讨论的选项是长度,而不是大小。

我会打开错误报告并尝试等待有关此主题的评论,因为我不相信此代码是意外。会报告回来..