Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Delphi [игнор отключен] [закрыт для гостей] / FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать / 25 сообщений из 30, страница 1 из 2
14.03.2020, 18:04
    #39937430
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
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
Код: plsql
1.
2.
3.
4.
5.
6.
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 показывает размер файла некорректно:
Код: plsql
1.
2.
3.
4.
5.
SQL> select octet_length(FILE_) from TEST_BLOB;

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



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

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

BLOBToDB
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
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
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
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?
...
Рейтинг: 0 / 0
14.03.2020, 18:16
    #39937431
Dmitry Arefiev
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
...
Рейтинг: 0 / 0
14.03.2020, 18:22
    #39937432
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Dmitry Arefiev,

Читал. С загрузкой в БД проблем нет, я писал. Проблема с загрузкой из БД и только на больших файлах - похоже на ошибку или ограничение самого FireDAC. В документации для select'а вместо вменяемого примера только SQL Server FILESTREAM.
...
Рейтинг: 0 / 0
14.03.2020, 19:43
    #39937438
Sinemurius
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
А попробуйте написать хранимку, которая получает ID строки в таблице TEST_BLOB, считывает блоб в переменную, разбивает на куски и возвращает блоб кусками.

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

Так Вы сможете помимо прочего показывать прогресс считывания.
...
Рейтинг: 0 / 0
14.03.2020, 19:59
    #39937446
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Sinemurius,

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

Коллбэк для прогресс-бара, по-хорошему, должен вызывать сам стрим в момент считывания очередного куска.
...
Рейтинг: 0 / 0
14.03.2020, 20:08
    #39937449
ъъъъъ
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,

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

https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html
...
Рейтинг: 0 / 0
14.03.2020, 20:29
    #39937452
ъъъъъ
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
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
...
Рейтинг: 0 / 0
14.03.2020, 20:53
    #39937459
Sinemurius
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos
Sinemurius,

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

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


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

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


Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
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 (кусок блоба)

Вы можете не скачивать весь файл, а скачивать кусками для использования. К примеру, если это видео, то скачиваете кусками и подкидываете куски плееру.
...
Рейтинг: 0 / 0
14.03.2020, 21:08
    #39937460
Sinemurius
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Можете повозиться и написать свой потомок от TStream, который будет обращаться к базе и вытаскивать блоб кусками.
...
Рейтинг: 0 / 0
14.03.2020, 21:39
    #39937462
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
ъъъъъ,

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

Sinemurius,

Попробую в порядке эксперимента.
...
Рейтинг: 0 / 0
14.03.2020, 22:05
    #39937469
Vlad F
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,

Продублируй вопрос в форме по IB/FB. Там хватает высоких ценителей егойного API, если у FireDAC-а в данном месте возможные проблемы с его использованием.
...
Рейтинг: 0 / 0
14.03.2020, 22:29
    #39937473
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Vlad F,

Дублировать 1-в-1 не стал, но ссылку сюда написал , если вдруг кто-то читает форум Firebird и не заходит в раздел Delphi.
...
Рейтинг: 0 / 0
14.03.2020, 22:39
    #39937478
Vlad F
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,

Вижу. Однако ты тему совсем неудачную там задал, малоинформативную и непривлекательную и не по существу, имхо.
...
Рейтинг: 0 / 0
15.03.2020, 11:57
    #39937530
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Sinemurius,

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

DDL
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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 ; ^




Результат выполнения:
Код: plsql
1.
2.
3.
4.
5.
SQL> select * from TEST_SPLIT(1);

        OCTET_LENGTH_           BIT_LENGTH_          CHAR_LENGTH_
===================== ===================== =====================
           -457250816             636960768            -457250816
...
Рейтинг: 0 / 0
15.03.2020, 12:08
    #39937532
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Vlad F,

Проверил IBX:

BLOBToFileIBX
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
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'у проблемы.
...
Рейтинг: 0 / 0
15.03.2020, 12:21
    #39937535
Sinemurius
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,

А length ?
...
Рейтинг: 0 / 0
15.03.2020, 12:24
    #39937538
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Sinemurius,

Нет такого.
...
Рейтинг: 0 / 0
15.03.2020, 13:50
    #39937560
Sinemurius
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Что-то смахивает на то, что блоб загружен в базу как то криво.

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

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

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

Ну и рассмотреть: что у Вас за блоб такой ? что в нем хранится ? можно ли это разбить на какие то структурированные куски ?
...
Рейтинг: 0 / 0
15.03.2020, 14:12
    #39937570
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Sinemurius,

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

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

С бóльшим размером блоба задача носит больше академический характер - обратить внимание разработчиков на неоднозначность документации, зафиксировать баги компонентов доступа, ISC API, функции octet_length.
...
Рейтинг: 0 / 0
15.03.2020, 14:29
    #39937576
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaosНо даже попытка скачать 2 ГБ падает с ООМ на CreateBlobStream, что в FireDAC, что в IBX.

Потому что автор IBX был рукожоп, а FireDAC унаследовал архитектуру.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
15.03.2020, 17:56
    #39937621
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Мне удалось скачать блоб 7.5 GB с помощью вызова ISC API напрямую.

Полный текст программы, C++
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
#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;
}




Контрольные суммы исходного файла и скаченного совпали.
...
Рейтинг: 0 / 0
15.03.2020, 18:09
    #39937626
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaosПолный текст программы, C++

Полагаешь, так много людей неспособны найти у Firebird каталог examples\api, что нужно его
здесь цитировать?.. Ну, наверное, ты прав.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
15.03.2020, 18:44
    #39937633
Vlad F
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,
Теперь нужен патч для FireDAC. Сделаешь очень доброе дело.
...
Рейтинг: 0 / 0
15.03.2020, 20:33
    #39937650
nicholaos
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
Vlad F,

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

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

Все это нужно и для IBX.
...
Рейтинг: 0 / 0
15.03.2020, 20:41
    #39937652
Vlad F
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать
nicholaos,

Да хрен бы с этим IBX (хотя я сам до сих пор на нем плотно сижу), - надо думать на перспективу. Сделай тикет на QC хотя бы для FireDAC, с указанием путей решения проблемы в целом.
...
Рейтинг: 0 / 0
Форумы / Delphi [игнор отключен] [закрыт для гостей] / FireDAC и очень большой BLOB (Firebird) - AV при попытке скачать / 25 сообщений из 30, страница 1 из 2
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]