TSQLQuery strumieniuje tylko pierwsze 1 MB danych poprawnie dla dużych łańcuchów

(patrz Edycja # 1 ze śledzeniem stosu i Edycja # 2 z obejściem na końcu postu)

Podczas rozwiązywania problemówTSQLQuery.FieldByName (). AsString -> TStringStream Uszkadza dane, Znalazłem, że aTSQLQuery.FieldByName().AsBytes będzie przesyłać tylko dokładnie 1 MBvarchar(max) dane poprawnie.

Używając WireShark, sprawdziłem, czy dane sąwszystko przekazywane do aplikacji Delphipoprawnie.Sprawdziłem tozawsze wypisuje poprawną liczbę bajtów do pliku wyjściowego, ale wszystkie bajty, które przekraczajądokładnie 1 MB są bajtami null.Do tego,TSQLQuery.FieldByName().AsString i.AsWideString wykazują również to samo zachowanie.

Co by to spowodowało.AsBytes dostarczyć prawidłową liczbę bajtów doTFileStream, alenull wszystkie bajty przekraczające 1 MB?

Przypadek testowy

Ten przypadek testowy tworzy dwa pliki wyjściowe.Plus14.txt wynosi 1 MB + 14 bajtów.Plus36.txt wynosi 1 MB + 36 bajtów. W obu przypadkach bajty są większe niż 1 MBnull wartości bajtów. Próbowałem nawet łańcucha 16 MB. Pierwsze 1 MB pliku wyjściowego było poprawne; następne 15 MB to wszystkonull bajty.

SQL Server
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

Domyślny formularz z tymTSQLConnection spadł na to (i jedenTButton):

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
Delphi PAS

Kod dlaTButton.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;
<<< Edytuj # 1 - Ślad stosu >>>

Przeszukałem kod Embarcadero i znalazłem miejsce, w którym znajduje sięnull najpierw pojawiają się bajty.

FMethodTable.FDBXRow_GetBytesData.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.ReadBlobDataData.SqlExpr.TSQLBlobStream.Read((no value),1048590)System.Classes.TStream.ReadBuffer((no value),1048590) 1MB + 14bData.DB.TBlobField.GetAsBytesUnit1.save('Plus14')

GdyFDBXRow_GetBytes wraca,Value: TBytes to 1048590 bajtów, znull wartości ustawione dla ostatnich 14 bajtów.

Nie jestem pewien, co spróbować dalej. Każda pomoc jest bardzo mile widziana.

<<< Edytuj # 2 - Obejście >>>

ustawiłemSQLConnection1.MaxBlobSize := 2097152, a teraz wszystkie bajty są poprawnie przesyłane do plików wyjściowych. Problem pojawia się tylko wtedy, gdy.MaxBlobSize = -1.

Pilna potrzeba rozwiązania problemu zniknęła, ponieważ znalazłem obejście. Jednak nadal chciałbym się dostać-1 działać, jeśli to możliwe, ponieważ wartości z mojej bazy danych czasami przekraczają 50 megabajtów. Tak więc wszelkie sugestie lub pomoc są nadal doceniane.

<<< Edytuj # 3 - Raport o błędzie >>>

Złożyłem raport o błędzie z Embarcadero (QC # 108475). Zgłosię raport, gdy błąd zostanie potwierdzony / naprawiony.

<<< Edytuj # 4 - Raport o eskalowanym błędzie >>>

Odkryłem dzisiaj, że użycie tego obejścia czasami powodujeTClientDataSet rzucićEOleException z tekstem ”Katastrofalna awaria” Najwyraźniej aTClientDataSet woli aMaxBlobSize := '-1';. W związku z tym eskalowałem raport o błędzie w Embarcadero. Mam nadzieję, że wkrótce dostarczą poprawkę lub lepsze obejście tego problemu.

questionAnswers(1)

yourAnswerToTheQuestion