TSQLQuery правильно передает только первые 1 МБ данных для больших строк

(см. Редактирование # 1 с трассировкой стека и Редактирование # 2 с обходным решением в конце публикации)

При устранении неполадокTSQLQuery.FieldByName (). AsString - & gt; TStringStream портит данныеЯ обнаружил, чтоTSQLQuery.FieldByName().AsBytes будет транслироваться только 1 МБvarchar(max) данные правильно.

Using WireShark, I verified that the data is all being handed to the Delphi app correctly. I verified that it always writes out the correct number of bytes to the output file, but any bytes that exceed exactly 1MB are null bytes. Additionally, TSQLQuery.FieldByName().AsString and .AsWideString also exhibit the same behavior.

Что вызвало бы.AsBytes предоставить правильное количество байтов дляTFileStream, ноnull все байты, которые превышают 1 МБ?

Test Case

Этот тестовый пример создает два выходных файла.Plus14.txt 1 МБ + 14 байт.Plus36.txt 1 МБ + 36 байт. В обоих случаях байты размером более 1 МБnull байтовые значения. Я даже попробовал строку 16 МБ. Первые 1 МБ выходного файла были правильными; следующие 15 МБ были всеnull байт.

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

Форма по умолчанию, с этимTSQLConnection упал на него (и один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
Delphi 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;
<<< Edit #1 - Stack Trace >>>

Я проследил код Embarcadero и нашел место, гдеnull байты сначала появляются.

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 байтов.

Я не уверен, что попробовать дальше. Любая помощь очень ценится.

<<< Edit #2 - Workaround >>>

Я поставилSQLConnection1.MaxBlobSize := 2097152и теперь все байты правильно передаются в выходные файлы. Таким образом, проблема возникает только тогда, когда.MaxBlobSize = -1.

Срочность решения проблемы ушла, когда я нашел обходной путь. Тем не менее, я все еще хотел бы получить-1 работать, если это возможно, так как значения из моей базы данных иногда превышают 50 мегабайт. Поэтому любые предложения или помощь по-прежнему приветствуются.

<<< Edit #3 - Bug Report >>>

Я отправил сообщение об ошибке в Embarcadero (QC # 108475). Я сообщу, как только ошибка будет подтверждена / исправлена.

<<< Edit #4 - Escalated Bug Report >>>

Сегодня я обнаружил, что использование этого обходного пути иногда вызываетTClientDataSet броситьEOleException с текстом & apos;Catastrophic Failure& APOS ;. ВидимоTClientDataSet предпочитаетMaxBlobSize := '-1';, Следовательно, я расширил отчет об ошибках на Embarcadero. Надеемся, что они предоставят исправление или лучший обходной путь для этого в ближайшее время.

Ответы на вопрос(1)

Ваш ответ на вопрос