Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
 FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Delphi Rio 10.3.3
Firebird 2.5.9, 32-х битный

Тестовая БД (page_size = 16384) с одной таблицей с одной записью с очень большим (не текстовым) BLOB'ом.

Размер исходного файла на диске - 7.57 GB (8,132,685,824 bytes)
Размер файла БД на диске - 7.73 GB (8,307,064,832 bytes)

+ DDL

CREATE TABLE TEST_BLOB (
    ID     INTEGER NOT NULL,
    FILE_  BLOB SUB_TYPE 0 SEGMENT SIZE 80
);

ALTER TABLE TEST_BLOB ADD PRIMARY KEY (ID);



isql показывает размер файла некорректно:
SQL> select octet_length(FILE_) from TEST_BLOB;

OCTET_LENGTH
============
  -457250816


Хотя по спецификации (https://firebirdsql.org/en/firebird-technical-specifications) поддерживаются BLOB'ы до 32 ГБ.

Загружал в БД следующим кодом:

+ BLOBToDB

procedure TForm1.btBLOBToDBClick(Sender: TObject);
var
  fs: TFileStream;
begin
  FDConnection1.Connected := True;
  FDTransaction1.StartTransaction;

  FDQuery1.SQL.Text := 'insert into TEST_BLOB(ID, FILE_) values(1, :FILE_)';
  FDQuery1.ParamByName('FILE_').DataType := ftStream;
  FDQuery1.ParamByName('FILE_').ParamType := ptInput;
  FDQuery1.ParamByName('FILE_').StreamMode := smOpenRead;
  FDQuery1.ExecSQL;
  fs := TFileStream.Create('C:\Work\disk.iso', fmOpenRead);
  try
    FDQuery1.ParamByName('FILE_').AsStream.CopyFrom(fs, -1);
  finally
    fs.Free;
  end;
  FDQuery1.CloseStreams;

  FDTransaction1.Commit;
  FDConnection1.Connected := False;
end;



Пытаюсь загрузить из БД:

+ BLOBToFile

procedure TForm1.btBLOBToFileClick(Sender: TObject);
var
  s: TStream;
  fs: TFileStream;
begin
  FDConnection1.Connected := True;
  FDTransaction1.StartTransaction;

  FDQuery1.FetchOptions.AutoFetchAll := afDisable;
  FDQuery1.FetchOptions.Cache := [fiDetails, fiMeta];
  FDQuery1.FetchOptions.Items := [fiDetails, fiMeta];

  FDQuery1.SQL.Text := 'select ID, FILE_ from TEST_BLOB where ID = :ID';
  FDQuery1.MasterFields := 'ID';
  FDQuery1.ParamByName('ID').AsInteger := 1;
  FDQuery1.Open;
  if not FDQuery1.Eof then begin
    fs := TFileStream.Create('C:\Work\disk2.iso', fmCreate);
    try
      s := FDQuery1.CreateBlobStream(FDQuery1.FieldByName('FILE_'), bmRead);
      fs.CopyFrom(s, -1);
    finally
      fs.Free;
    end;
  end;
  FDQuery1.CloseStreams;
  FDQuery1.Close;

  FDTransaction1.Commit;
  FDConnection1.Connected := False;
end;



Падает на CreateBlobStream с ошибкой Access Violation. Стэк вызовов приводит к Lib.Fisc_get_segment в TIBBlob.Read (FireDAC.Phys.IBWrapper.pas).

Маленькие файлы сохраняются без проблем.

Вопрос - есть ли известные способы работы с большими блобами? Другие компоненты доступа, например? IBEScript.dll не справился. Известная UDF BlobSaveLoad справляется, но работает только на сервере.
Придется писать самому на ISC API?
14 мар 20, 18:04    [22099105]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Dmitry Arefiev
Member

Откуда:
Сообщений: 9795
RTFM
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Support_for_Blob_Streaming_in_FireDAC#External_Streams
14 мар 20, 18:16    [22099107]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Dmitry Arefiev,

Читал. С загрузкой в БД проблем нет, я писал. Проблема с загрузкой из БД и только на больших файлах - похоже на ошибку или ограничение самого FireDAC. В документации для select'а вместо вменяемого примера только SQL Server FILESTREAM.
14 мар 20, 18:22    [22099108]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Sinemurius
Member

Откуда:
Сообщений: 131
А попробуйте написать хранимку, которая получает ID строки в таблице TEST_BLOB, считывает блоб в переменную, разбивает на куски и возвращает блоб кусками.

Клиент используя процедурку прочитает блоб кусками и соединит их уже средствами Delphi.

Так Вы сможете помимо прочего показывать прогресс считывания.
14 мар 20, 19:43    [22099129]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Sinemurius,

У меня не текстовый блоб размером 7 ГБ. Если разбить его на куски в хранимке вообще технически возможно, то навряд ли это правильное решение.

Коллбэк для прогресс-бара, по-хорошему, должен вызывать сам стрим в момент считывания очередного куска.
14 мар 20, 19:59    [22099140]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
ъъъъъ
Member

Откуда:
Сообщений: 198
nicholaos,

Максимальный размер поля BLOB ограничен 4 ГБ. Для размера страницы 4 КБ (4096 байт) максимальный размер меньше - чуть меньше 2 ГБ.

https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html
14 мар 20, 20:08    [22099145]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
ъъъъъ
Member

Откуда:
Сообщений: 198
nicholaos
Хотя по спецификации (https://firebirdsql.org/en/firebird-technical-specifications) поддерживаются BLOB'ы до 32 ГБ.


Смотри официальную документацию, а не фик знает что.
Вот тут руководство по 2.5 скачать можно:

https://www.sql.ru/forum/1123374/ann-rukovodstvo-po-yazyku-sql-subd-firebird-2-5
14 мар 20, 20:29    [22099154]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Sinemurius
Member

Откуда:
Сообщений: 131
nicholaos
Sinemurius,

У меня не текстовый блоб размером 7 ГБ. Если разбить его на куски в хранимке вообще технически возможно, то навряд ли это правильное решение.

Коллбэк для прогресс-бара, по-хорошему, должен вызывать сам стрим в момент считывания очередного куска.


Бинарный блоб спокойно режится substring-ом.

Примерно так:


Pos = 1;
BlobNumPart = 1;
FilePartLength = 1024 * 1024;
select length(FILE_) from test_blob where id = :id into FileLength;

while Pos <= FileLength do 
begin
  select substring(file_ from pos for FilePartLength) from test_blob where id = :id into BlobPart;
  suspend;
  Pos = Pos + FilePartLength;
  BlobNumPart = BlobNumPart + 1;
end


Где выходные параметры хранимки - BlobNumPart (номер куска) и BlobPart (кусок блоба)

Вы можете не скачивать весь файл, а скачивать кусками для использования. К примеру, если это видео, то скачиваете кусками и подкидываете куски плееру.

Сообщение было отредактировано: 14 мар 20, 21:02
14 мар 20, 20:53    [22099164]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Sinemurius
Member

Откуда:
Сообщений: 131
Можете повозиться и написать свой потомок от TStream, который будет обращаться к базе и вытаскивать блоб кусками.
14 мар 20, 21:08    [22099168]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
ъъъъъ,

Загрузил файл размером 2.78 GB - аналогичная ошибка.

Sinemurius,

Попробую в порядке эксперимента.
14 мар 20, 21:39    [22099175]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Vlad F
Member

Откуда:
Сообщений: 1172
nicholaos,

Продублируй вопрос в форме по IB/FB. Там хватает высоких ценителей егойного API, если у FireDAC-а в данном месте возможные проблемы с его использованием.
14 мар 20, 22:05    [22099183]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Vlad F,

Дублировать 1-в-1 не стал, но ссылку сюда написал, если вдруг кто-то читает форум Firebird и не заходит в раздел Delphi.
14 мар 20, 22:29    [22099189]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Vlad F
Member

Откуда:
Сообщений: 1172
nicholaos,

Вижу. Однако ты тему совсем неудачную там задал, малоинформативную и непривлекательную и не по существу, имхо.
14 мар 20, 22:39    [22099194]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Sinemurius,

В хранимке разрезать блоб не получилось, все функции, возвращающие длину, работают некорректно. Проверял так:

+ DDL

SET TERM ^ ;

create or alter procedure TEST_SPLIT (
    ID integer)
returns (
    OCTET_LENGTH_ bigint,
    BIT_LENGTH_ bigint,
    CHAR_LENGTH_ bigint)
as
begin
  select octet_length(FILE_), bit_length(FILE_), char_length(FILE_)
  from TEST_BLOB
  where ID = :ID
  into :OCTET_LENGTH_, :BIT_LENGTH_, :CHAR_LENGTH_;
  suspend;
end^

SET TERM ; ^



Результат выполнения:
SQL> select * from TEST_SPLIT(1);

        OCTET_LENGTH_           BIT_LENGTH_          CHAR_LENGTH_
===================== ===================== =====================
           -457250816             636960768            -457250816
15 мар 20, 11:57    [22099298]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Vlad F,

Проверил IBX:

+ BLOBToFileIBX

procedure TForm1.btBLOBToFileIBXClick(Sender: TObject);
var
  s: TStream;
  fs: TFileStream;
begin
  IBDatabase1.Open;
  IBQuery1.SQL.Text := 'select ID, FILE_ from TEST_BLOB where ID = :ID';
  IBQuery1.ParamByName('ID').AsInteger := 1;
  IBQuery1.Open;
  if not IBQuery1.Eof then begin
    fs := TFileStream.Create('C:\Work\disk.iso', fmCreate);
    try
      s := IBQuery1.CreateBlobStream(IBQuery1.FieldByName('FILE_'), bmRead);
      fs.CopyFrom(s, -1);
    finally
      fs.Free;
    end;
  end;
  IBQuery1.Close;
  IBDatabase1.Close;
end;



Падает с Range Check Error на fs.CopyFrom. Отладчик приводит к GetBlobInfo (IBX.IBBlob.pas). Функция isc_portable_integer возвращает для TotalSize некорректное значение (см. картинку).

+ GetBlobInfo

Картинка с другого сайта.


Похоже все упирается в ISC API у которого аналогичные octet_length'у проблемы.
15 мар 20, 12:08    [22099300]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Sinemurius
Member

Откуда:
Сообщений: 131
nicholaos,

А length ?
15 мар 20, 12:21    [22099303]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Sinemurius,

Нет такого.
15 мар 20, 12:24    [22099306]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Sinemurius
Member

Откуда:
Сообщений: 131
Что-то смахивает на то, что блоб загружен в базу как то криво.

Вообще хранить 7 гигабайтные блобы - это как то неэлегантно что-ли. Вы уверены, что Вам именно это нужно ?

Я бы рассмотрел вопрос о том, чтобы хранить не в базе, а в файловой системе. Ну или разбивать блоб на куски в момент загрузки блоба в базе.

Ну к примеру сделать связанную с Вашим TEST_BLOB табличку для хранения кусков блоба.

Ну и рассмотреть: что у Вас за блоб такой ? что в нем хранится ? можно ли это разбить на какие то структурированные куски ?
15 мар 20, 13:50    [22099341]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Sinemurius,

Не элегантно, согласен. Для прикладной задачи мне достаточно и 2-х ГБ.

Но даже попытка скачать 2 ГБ падает с ООМ на CreateBlobStream, что в FireDAC, что в IBX.

С бóльшим размером блоба задача носит больше академический характер - обратить внимание разработчиков на неоднозначность документации, зафиксировать баги компонентов доступа, ISC API, функции octet_length.
15 мар 20, 14:12    [22099351]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 49515

nicholaos
Но даже попытка скачать 2 ГБ падает с ООМ на CreateBlobStream, что в FireDAC, что в IBX.

Потому что автор IBX был рукожоп, а FireDAC унаследовал архитектуру.

Posted via ActualForum NNTP Server 1.5

15 мар 20, 14:29    [22099363]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Мне удалось скачать блоб 7.5 GB с помощью вызова ISC API напрямую.

+ Полный текст программы, C++

#include <stdlib.h>
#include <ibase.h>
#include <fstream>
#include <iostream>

#define DATABASE_PATH    "C:\\Work\\TEST_BLOB.FDB"
#define OUTPUT_FILE_PATH "C:\\Work\\disk2.iso"
#define SQL_SELECT       "select FILE_ from TEST_BLOB where ID = '1'"

int main()
{
  char database[] = DATABASE_PATH;

  isc_db_handle db = 0;
  isc_tr_handle trans;

  ISC_STATUS status_vector[20];

  short dpb_buf_len = 20;

  char dpb_buf[] = {
				isc_dpb_version1,
				isc_dpb_user_name,
				6,
				'S','Y','S','D','B','A',
				isc_dpb_password,
				9,
				'm','a','s','t','e','r','k','e','y'
			  };

  isc_attach_database(status_vector, strlen(database), database, &db, dpb_buf_len, dpb_buf);

  if (status_vector[0] == 1 && status_vector[1])
  {
	isc_print_status(status_vector);

	return 1;
  }

  char *select = SQL_SELECT;

  XSQLDA *out_sqlda;

  out_sqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(1));

  out_sqlda->version = SQLDA_VERSION1;

  out_sqlda->sqln = 1;

  isc_stmt_handle stmt;

  stmt = NULL;

  isc_dsql_allocate_statement(status_vector, &db, &stmt);

  char isc_tpb[] = {
				isc_tpb_version3,
				isc_tpb_read,
				isc_tpb_wait,
				isc_tpb_read_committed,
				isc_tpb_no_rec_version
			 };

  isc_start_transaction(status_vector, &trans, 1, &db, sizeof(isc_tpb), isc_tpb);
  if (status_vector[0] == 1 && status_vector[1])
  {
	isc_print_status(status_vector);

	return 1;
  }

  isc_dsql_prepare(status_vector, &trans, &stmt, 0, select, SQLDA_VERSION1, out_sqlda);

  ISC_QUAD blob_id;

  short flag;

  out_sqlda->sqlvar[0].sqldata = (char *) &blob_id;

  out_sqlda->sqlvar[0].sqltype = SQL_BLOB + 1;

  out_sqlda->sqlvar[0].sqllen = sizeof(ISC_QUAD);

  out_sqlda->sqlvar[0].sqlind = &flag;

  isc_dsql_execute(status_vector, &trans, &stmt, 1, NULL);

  ISC_STATUS fetch_stat;

  long SQLCODE;

  while ((fetch_stat = isc_dsql_fetch(status_vector, &stmt, 1, out_sqlda)) == 0) {}

  if (fetch_stat != 100L)
  {
    SQLCODE = isc_sqlcode(status_vector);

    isc_print_sqlerror(SQLCODE, status_vector);

    return 1;
  }

  isc_blob_handle blob_handle;

  blob_handle = NULL;

  char blob_segment[80];

  unsigned short actual_seg_len;

  isc_open_blob2(status_vector, &db, &trans, &blob_handle, &blob_id, 0, NULL);

  ISC_STATUS blob_stat;

  blob_stat = isc_get_segment(status_vector,
                              &blob_handle,
                              &actual_seg_len,
                              sizeof(blob_segment),
                              blob_segment);

  ofstream stream;

  stream.open(OUTPUT_FILE_PATH, std::ios_base::binary);

  while (blob_stat == 0 || status_vector[1] == isc_segment)
  {
    stream.write(&blob_segment[0], actual_seg_len);

    blob_stat = isc_get_segment(status_vector,
                                &blob_handle,
                                &actual_seg_len,
                                sizeof(blob_segment),
                                blob_segment);
  };

  stream.close();

  isc_close_blob(status_vector, &blob_handle);

  isc_commit_transaction(status_vector, &trans);

  if (db)
    isc_detach_database(status_vector, &db);

  return 0;
}



Контрольные суммы исходного файла и скаченного совпали.
15 мар 20, 17:56    [22099458]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 49515

nicholaos
Полный текст программы, C++

Полагаешь, так много людей неспособны найти у Firebird каталог examples\api, что нужно его
здесь цитировать?.. Ну, наверное, ты прав.

Posted via ActualForum NNTP Server 1.5

15 мар 20, 18:09    [22099468]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Vlad F
Member

Откуда:
Сообщений: 1172
nicholaos,
Теперь нужен патч для FireDAC. Сделаешь очень доброе дело.
15 мар 20, 18:44    [22099482]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
nicholaos
Member

Откуда:
Сообщений: 83
Vlad F,

Патч - это когда можно обойтись простым фиксом. Тут нужен полноценный пул-реквест.

Во-первых, нужно отовсюду удалять использование TIBBlob.total_length - он возвращает неправильное значение.
Во-вторых, нужно в CreateBlobStream научится не аллоцировать память полностью и работать аналогично файловому стриму - читать данные по мере их запрашивания в методе CopyFrom.

Все это нужно и для IBX.
15 мар 20, 20:33    [22099510]     Ответить | Цитировать Сообщить модератору
 Re: FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать  [new]
Vlad F
Member

Откуда:
Сообщений: 1172
nicholaos,

Да хрен бы с этим IBX (хотя я сам до сих пор на нем плотно сижу), - надо думать на перспективу. Сделай тикет на QC хотя бы для FireDAC, с указанием путей решения проблемы в целом.
15 мар 20, 20:41    [22099514]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
Все форумы / Delphi Ответить