Delphi/FireDACStrsTrim2Len 对 ParamByName 没有影响

Delphi/FireDAC StrsTrim2Len has no impact on ParamByName

使用 Delphi XE7 和 Tokyo with Firebird 2.5 我得出的结论是 StrsTrim2Len 在使用 TFDQuery 和 [= 进行 updates/inserts 时没有任何影响16=],这使得过大的字符串引发异常。

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

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

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

不,当您启用 StrsTrim2Len 选项时,即使分配的参数值也应 trim 达到绑定字段的限制。因此,对于字符串字段参数,直接字符串赋值应该 trim 的值 in length,恕我直言:

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

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

自动字符串参数值有什么问题trimming then?

FireDAC,现在为 Firebird 驱动程序实现,trims 参数数据缓冲区仅当 数据大小[=编码参数值的47=]超过参数字段的存储数据大小(启用StrsTrim2Len选项时)。所以它是基于数据 size 而不是字符串 length.

参数数据大小(如果未指定)似乎是从 RDB$FIELD_LENGTH RDB$FIELDS[=47 的元字段中获得的=] 系统 table (无法确认,但似乎 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);

你可能猜到了,参数值缓冲区的 trimming 实际上会发生,但是编码后的字符串太小了,所以 trimming 被跳过了。你也可以预测,如果你使用这样的参数,例如对于插入值,您最终会遇到引擎异常,因为引擎在验证约束时使用插入的字符串值长度而不是数据大小。

我在这里找不到处理数据大小的实际意义。好吧,你永远不会溢出存储数据大小,但另一方面你可以很容易地超过字段的长度。并且讨论的选项是关于长度,而不是关于大小。

我将打开一个错误报告并尝试等待有关此主题的一些评论,因为我不认为这段代码是意外。会回来报告..