3

(投稿の最後にスタックトレースを含む編集#1と回避策を含む編集#2を参照してください)

TSQLQuery.FieldByName().AsString -> TStringStream Corrupts Dataのトラブルシューティング中に、TSQLQuery.FieldByName().AsBytes正確に 1MB のvarchar(max)データのみが正しくストリーミングされることがわかりました。

  • WireShark を使用して、データがすべてDelphi アプリに正しく渡されていることを確認しました。
  • 常に正しいバイト数が出力ファイルに書き出されることを確認しましたが、正確に 1MBを超えるバイトはすべて null バイトです。
  • さらに、TSQLQuery.FieldByName().AsStringまた.AsWideString、同じ動作を示します。

.AsBytesに正しいバイト数を提供する原因は何ですか?TFileStreamただし、nullすべてのバイトは 1MB を超えていますか?

テストケース

このテスト ケースでは、2 つの出力ファイルが作成されます。 Plus14.txt1MB + 14 バイトです。 Plus36.txt1MB + 36 バイトです。どちらの場合も、1MB を超えるバイトはnullバイト値です。16MBの文字列も試しました。出力ファイルの最初の 1MB は正しかったです。次の 15MB はすべてnullバイトでした。

SQLサーバー

use tempdb
go
create procedure RunMe
as
  declare @s1 varchar(max), @s2 varchar(max)

  set @s1 = '0123456789ABCDEF'
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 128 bytes
  set @s1 = @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 -- 1,024 bytes
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 8,192 bytes
  set @s1 = @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 -- 65,536 bytes
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 524,288 bytes
  set @s1 = @s2 + @s2                                     -- 1,048,576 bytes

  set @s2 = @s1 + 'this is a test'                        -- 1MB + 14 bytes
  set @s1 = @s1 + 'of the emergency broadcasting system'  -- 1MB + 36 bytes

  select @s2 as Plus14, @s1 as Plus36
go
grant execute on RunMe to public
go

Delphi DFM

TSQLConnectionこれがドロップされたデフォルトのフォーム(および1つTButton):

object SQLConnection1: TSQLConnection
  DriverName = 'MSSQL'
  GetDriverFunc = 'getSQLDriverMSSQL'
  LibraryName = 'dbxmss.dll'
  LoginPrompt = False
  Params.Strings = (
    'User_Name=user'
    'Password=password'
    'SchemaOverride=%.dbo'
    'DriverUnit=Data.DBXMSSQL'

      'DriverPackageLoader=TDBXDynalinkDriverLoader,DBXCommonDriver160.' +
      'bpl'

      'DriverAssemblyLoader=Borland.Data.TDBXDynalinkDriverLoader,Borla' +
      'nd.Data.DbxCommonDriver,Version=16.0.0.0,Culture=neutral,PublicK' +
      'eyToken=91d62ebb5b0d1b1b'

      'MetaDataPackageLoader=TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDr' +
      'iver160.bpl'

      'MetaDataAssemblyLoader=Borland.Data.TDBXMsSqlMetaDataCommandFact' +
      'ory,Borland.Data.DbxMSSQLDriver,Version=16.0.0.0,Culture=neutral' +
      ',PublicKeyToken=91d62ebb5b0d1b1b'
    'GetDriverFunc=getSQLDriverMSSQL'
    'LibraryName=dbxmss.dll'
    'VendorLib=sqlncli10.dll'
    'VendorLibWin64=sqlncli10.dll'
    'HostName=localhost'
    'Database=tempdb'
    'MaxBlobSize=-1'
    'LocaleCode=0000'
    'IsolationLevel=ReadCommitted'
    'OSAuthentication=False'
    'PrepareSQL=True'
    'BlobSize=-1'
    'ErrorResourceFile='
    'OS Authentication=True'
    'Prepare SQL=False')
  VendorLib = 'sqlncli10.dll'
  Left = 8
  Top = 8
end

デルファイ PAS

のコードTButton.OnClick:

procedure TForm1.Button1Click(Sender: TObject);
var qry: TSQLQuery;

  procedure save(str: string);
  var data: TBytes; fs: TFileStream;
  begin
    fs := TFileStream.Create(Format('c:\%s.txt', [str]), fmCreate);
    try
      data := qry.FieldByName(str).AsBytes;
      if data <> nil then
        fs.WriteBuffer(data[0], Length(data));
    finally
      FreeAndNil(fs);
    end;
  end;

begin
  SQLConnection1.Open;
  qry := TSQLQuery.Create(nil);
  try
    qry.MaxBlobSize := -1;
    qry.SQLConnection := SQLConnection1;
    qry.SQL.Text := 'set nocount on; exec RunMe';
    qry.Open;
    save('Plus14');
    save('Plus36');
  finally
    FreeAndNil(qry);
  end;
  SQLConnection1.Close;
end;

<<< 編集 #1 - スタック トレース >>>

null私は Embarcadero のコードをたどって、バイトが最初に現れる場所を見つけました。

  • FMethodTable.FDBXRow_GetBytes
  • Data.DBXDynalink.TDBXDynalinkByteReader.GetBytes(0,0,(...),0,1048590,True)
  • Data.SqlExpr.TCustomSQLDataSet.GetFieldData(1,$7EC80018)
  • Data.SqlExpr.TCustomSQLDataSet.GetFieldData(???,$7EC80018)
  • Data.DB.TDataSet.GetFieldData($66DB18,$7EC80018,True)
  • Data.SqlExpr.TSQLBlobStream.ReadBlobData
  • Data.SqlExpr.TSQLBlobStream.Read((no value),1048590)
  • System.Classes.TStream.ReadBuffer((no value),1048590) 1MB + 14b
  • Data.DB.TBlobField.GetAsBytes
  • Unit1.save('Plus14')

FDBXRow_GetBytes戻るときValue: TBytesは 1048590 バイトでnull、最後の 14 バイトに値が設定されています。

次に何を試せばよいかわかりません。どんな助けでも大歓迎です。

<<< 編集 #2 - 回避策 >>>

を設定SQLConnection1.MaxBlobSize := 2097152すると、すべてのバイトが出力ファイルに正しくストリーミングされるようになりました。そのため、問題が発生するのは.MaxBlobSize = -1.

回避策を見つけたので、問題を修正する緊急性はなくなりました。-1ただし、データベースの値が 50 MB を超える場合があるため、可能であれば作業を開始したいと考えています。したがって、提案やヘルプは引き続き歓迎されます。

<<< 編集 #3 - バグレポート >>>

Embarcadero にバグ レポートを提出しました (QC #108475)。バグが確認/修正されたら、また報告します。

<<< 編集 #4 - エスカレートされたバグ レポート >>>

今日、この回避策を使用すると、「壊滅的な失敗」というテキストでTClientDataSetがスローされることがあることがわかりました。どうやら aは aを好むようです。そのため、エンバカデロでバグ レポートをエスカレーションしました。うまくいけば、彼らはすぐにこれに対する修正またはより良い回避策を提供します.EOleExceptionTClientDataSetMaxBlobSize := '-1';

4

1 に答える 1

1

問題を回避することができました。TSQLConnection次のようにプロパティを設定するだけでよい場合があります。

sqlcon.Params.Values['MaxBlobSize'] := '250000000'; // 250 megs
sqlcon.Params.Values['BlobSize'] := '-1';

DBXRegDBしかし、ユニットを使用してすべてのTSQLConnectionコンポーネントをセットアップすることで、これらよりも多くのプロパティを設定しました。IDE のプロパティ エディタは使用しません...DBXRegDB回避策を機能させるには、ユニットの設定の一部も必要になる場合があります (確かなことはわかりません)。念のため、DBXRegDBユニットと使用方法の説明書を同梱します。

  • DBXRegDBユニットをファイルに追加し.dprます。
  • DBXRegDBフォームのuses句に追加します。
  • TSQLConnection次のコードを実行して、フォームのコンポーネントに渡します。

.

procedure SetupMSSqlConnection(const sqlcon: TSQLConnection; const hostname, port, maxcon, dbname, username, password: string);
begin
  sqlcon.Params.Clear;
  sqlcon.DriverName := 'MSSQL_Con';
  sqlcon.VendorLib := sqlcon.Params.Values[TDBXPropertyNames.VendorLib];
  sqlcon.LibraryName := sqlcon.Params.Values[TDBXPropertyNames.LibraryName];
  sqlcon.GetDriverFunc := sqlcon.Params.Values[TDBXPropertyNames.GetDriverFunc];

  sqlcon.Params.Values[TDBXPropertyNames.HostName] := hostname;
  sqlcon.Params.Values[TDBXPropertyNames.Port]     := port;
  sqlcon.Params.Values[TDBXPropertyNames.Database] := dbname;
  sqlcon.Params.Values[TDBXPropertyNames.UserName] := username;
  sqlcon.Params.Values[TDBXPropertyNames.Password] := password;
end;

最後に、TSQLQuery.MaxBlobSizeを '0' に設定して、 から値を自動的にコピーしますTSQLConnection

DBXRegDB使いたい人向けのユニットです。ここで見つけたものから適応させました: DBX without deploy DBXDrivers.iniBlobSizeを250 MB に設定しないでください。そうしないと、メモリ不足エラーが発生します。

unit DBXRegDB;

interface

implementation

uses
  DBXCommon, DBXDynalinkNative, DBXMSSQL, Forms, Classes;

type
  TDBXInternalDriver = class(TDBXDynalinkDriverNative)
  public
    constructor Create(DriverDef: TDBXDriverDef); override;
  end;

  TDBXInternalProperties = class(TDBXProperties)
  public
    constructor Create(DBXContext: TDBXContext); override;
  end;

{ TDBXInternalDriver }

constructor TDBXInternalDriver.Create(DriverDef: TDBXDriverDef);
begin
  inherited Create(DriverDef, TDBXDynalinkDriverLoader);
  InitDriverProperties(TDBXInternalProperties.Create(DriverDef.FDBXContext));
end;

{ TDBXInternalProperties }

constructor TDBXInternalProperties.Create(DBXContext: TDBXContext);
begin
  inherited Create(DBXContext);

  Values[TDBXPropertyNames.SchemaOverride]         := '%.dbo';
  Values[TDBXPropertyNames.DriverUnit]             := 'DBXMSSQL';
  Values[TDBXPropertyNames.DriverPackageLoader]    := 'TDBXDynalinkDriverLoader,DBXCommonDriver160.bpl';
  Values[TDBXPropertyNames.DriverAssemblyLoader]   := 'Borland.Data.TDBXDynalinkDriverLoader,Borland.Data.DbxCommonDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.MetaDataPackageLoader]  := 'TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDriver160.bpl';
  Values[TDBXPropertyNames.MetaDataAssemblyLoader] := 'Borland.Data.TDBXMsSqlMetaDataCommandFactory,Borland.Data.DbxMSSQLDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.GetDriverFunc]          := 'getSQLDriverMSSQL';
  Values[TDBXPropertyNames.LibraryName]            := 'dbxmss.dll';
  Values[TDBXPropertyNames.VendorLib]              := 'sqlncli10.dll';
  Values[TDBXPropertyNames.HostName]               := 'ServerName';
  Values[TDBXPropertyNames.Database]               := 'Database Name';
  Values[TDBXPropertyNames.MaxBlobSize]            := '250000000';
  Values['LocaleCode']                             := '0000';
  Values[TDBXPropertyNames.IsolationLevel]         := 'ReadCommitted';
  Values['OSAuthentication']                       := 'False';
  Values['PrepareSQL']                             := 'True';
  Values[TDBXPropertyNames.UserName]               := 'user';
  Values[TDBXPropertyNames.Password]               := 'password';
  Values['BlobSize']                               := '-1';
  Values[TDBXPropertyNames.ErrorResourceFile]      := '';
  Values['OS Authentication']                      := 'False';
  Values['Prepare SQL']                            := 'True';
  Values[TDBXPropertyNames.ConnectTimeout]         := '30';
end;

var
  InternalConnectionFactory: TDBXMemoryConnectionFactory;

initialization

  TDBXDriverRegistry.RegisterDriverClass('MSSQL_Con', TDBXInternalDriver);
  InternalConnectionFactory := TDBXMemoryConnectionFactory.Create;
  InternalConnectionFactory.Open;
  TDBXConnectionFactory.SetConnectionFactory(InternalConnectionFactory);

end.
于 2012-09-07T06:38:00.877 に答える