В «Форсайт. Аналитическая платформа» версии 10.9 и ниже для хранения двоичных данных в СУБД PostgreSQL используется тип данных LO. Начиная с версии 10.10, в качестве замены типа данных LO при создании новых репозиториев используется тип данных BYTEA по умолчанию.
Репозитории, созданные в предыдущих версиях продукта и использующие тип данных LO, поддерживаются без ограничений. При необходимости можно настроить репозиторий для работы с типом данных BYTEA вместо LO за счёт преобразования системных и пользовательских таблиц, заменив поля с типом данных LO на поля с типом данных BYTEA. Системные таблицы преобразовываются автоматически в процессе обновления версии репозитория средствами менеджера репозитория, пользовательские таблицы преобразовываются вручную.
Примечание. Одновременное использование типа данных LO и BYTEA в рамках одного репозитория недоступно.
Перед преобразованием системных и пользовательских таблиц выполните резервное копирование целевого репозитория и базы данных.
Для преобразования системных и пользовательских таблиц, содержащих поля с типом данных LO, в таблицы, содержащие поля с типом данных BYTEA, выполните шаги, указанные ниже.
При использовании типа данных BYTEA размер объекта, связанного с таблицей, не должен превышать 1 ГБ. Таблицы, содержащие поля с типом данных LO, могут быть связаны с объектами размером более 1 ГБ. Для поиска таких объектов создайте в репозитории модуль с идентификатором BLOB_FINDER.
Примечание. Модуль используется в качестве примера. При необходимости используйте другие способы поиска объектов.
Для выполнения модуля задайте переменные:
DATABASE_OBJECT_ID. Идентификатор базы данных в репозитории. По умолчанию: METABASE_DB;
SCHEMA_NAME. Идентификатор схемы. По умолчанию: public.
Примечание. Идентификаторы схемы, указанные в параметре «Схема» на странице «Свойства базы данных» и в переменной SCHEMA_NAME, должны совпадать.
Добавьте ссылки на системные сборки: Dal, Db, Metabase, IO.
Const
DATABASE_OBJECT_ID = "METABASE_DB"; // Идентификатор базы данных СУБД PostgreSQL
SCHEMA_NAME = "'public'"; // Идентификатор схемы, используемый в SQL-запросе SQL_GET_LO_TABLES
// Получим список таблиц,
содержащих поля с типом данных LO, с помощью запроса
SQL_GET_LO_TABLES = "SELECT a.table_name, a.column_name FROM " +
"information_schema.columns a, " +
"information_schema.tables b " +
"WHERE a.table_schema = " + SCHEMA_NAME + " AND " +
"a.table_name = b.table_name AND " +
"a.table_schema = b.table_schema AND " +
"a.udt_name='lo' AND " +
"b.table_type='BASE TABLE' AND " +
"(a.table_name NOT LIKE 'b_%' OR " +
"a.table_name = 'b_mod')";
Var
DB: IDatabaseInstance;
Com, Com_loId: IDalCommand;
Cur, Cur_loId: IDalCursor;
CurFields: IDalCursorFields;
NativeNameStr: String;
FieldName: String;
lo_size: Double;
StreamData: IIOStream;
Mb: IMetabase;
MbObjDescs: IMetabaseObjectDescriptors;
MbObjDesc: IMetabaseObjectDescriptor;
obj_key: Integer;
obj_id: String;
ObjCounter: Integer = 0;
ObjList: String = "";
// Проверим связь отношения в схеме с объектом репозитория
Public Function MatchRelationWithMetabaseObject(Desc: IMetabaseObjectDescriptor; SearchCube:boolean):IMetabaseObjectDescriptor;
Var
RDSParent: IMetabaseObjectDescriptor;
MbObjDescs: IMetabaseObjectDescriptors;
Begin
If Desc <> Null Then
Desc := Desc.Parent;
RDSParent := Desc;
If Not SearchCube Then
Debug.WriteLine("");
Debug.WriteLine("ВНИМАНИЕ: Куб " + RDSParent.Id + " содержит кеш размером более 1 Гб, необходимо удалить данные кеша");
ObjList := ObjList + " " + RDSParent.Id;
Debug.WriteLine("");
End If;
If SearchCube Then
MbObjDescs := RDSParent.Dependents;
For Each RDSParent In MbObjDescs Do
If RDSParent.ClassId=metabaseobjectclass.KE_CLASS_STDCUBE Then
Debug.WriteLine("");
Debug.WriteLine("ВНИМАНИЕ: Куб " + RDSParent.Id + " - содержит вложения размером более 1 Гб, необходимо открепить такие вложения от куба");
Debug.WriteLine("");
ObjList := ObjList + " " + RDSParent.Id;
End If;
End For;
End If;
Else
Debug.WriteLine("");
Debug.WriteLine("Для отношения " + NativeNameStr + " не удалось установить соответствие объекту репозитория");
Debug.WriteLine("Необходимо удалить указанный объект средствами СУБД");
Debug.WriteLine("");
ObjList := ObjList + " " + NativeNameStr;
End If;
Return RDSParent;
End Function MatchRelationWithMetabaseObject;
// Инициализируем выполнение Dal-команды в зависимости от типа проверяемого объекта
Sub ExecDalCom(ObjectType: String);
Begin
Com_loId := DB.Connection.CreateCommand("");
// Сформируем SQL-запрос в зависимости от типа объектов, которые необходимо
проверить
Select Case ObjectType
// Произведём выборку из таблицы b_mod
для проверки размера документов
Case "Documents":
Com_loId.SQL := "SELECT bin, obj FROM b_mod ORDER BY obj DESC";
Else
// Учтём
кавычки, т.к. в СУБД имя может быть идентификатором и его нужно передавать в запросе в двойных кавычках
Com_loId.SQL := "SELECT " + """" + FieldName + """" + " FROM " + """" + NativeNameStr + """";
End Select;
Com_loId.Execute;
Com_loId.Parse;
Cur_loId := Com_loId.CreateCursor;
While Not Cur_loId.Eof Do
If Cur_loId.Fields.Item(0).Value <> 0 Then
Select Case ObjectType
Case "Documents":
obj_key := Cur_loId.Fields.Item(1).Value;
End Select;
StreamData := IOStream.FromVariant(Cur_loId.Fields.Item(0).Value);
lo_size := StreamData.Size;
If lo_size > 1073741824 Then
Select Case ObjectType
// Выполним поиск кубов, под которыми находятся таблицы с вложениями
Case "Attachments":
Debug.WriteLine("");
Debug.WriteLine("Отношение " + NativeNameStr + " содержит Lo-объект размером " + lo_size.ToString + " байт (более 1 Гб)");
Debug.WriteLIne("Попытка установить связь с объектом репозитория...");
ObjCounter := ObjCounter+1;
obj_id := string.Replace(NativeNameStr,"t","T_OBJ");
MbObjDesc := mb.ItemByIdNameSpace(obj_id, integer.Parse(obj_id.Replace(obj_id, "T_OBJ", "")));
MbObjDesc := MatchRelationWithMetabaseObject(MbObjDesc, True);
// Выполним поиск кубов, для которых используется
кеширование
Case "CubeCacheTables":
Debug.WriteLine("");
Debug.WriteLine("Отношение " + NativeNameStr + " содержит Lo-объект размером " + lo_size.ToString + " байт (более 1 Гб)");
Debug.WriteLine("Попытка установить связь с объектом репозитория...");
ObjCounter := ObjCounter+1;
obj_id := string.Replace(string.ToUpper(NativeNameStr), "_CB_COLS_TAB","");
obj_key := integer.Parse(string.Replace(obj_id, "OBJ", ""));
MbObjDesc := mb.Item(obj_key);
MbObjDesc := MatchRelationWithMetabaseObject(MbObjDesc, False);
// Выполним поиск документов
Case "Documents":
obj_id := MB.Item(obj_key).Id;
Debug.WriteLine("");
Debug.WriteLine("ВНИМАНИЕ: в документе репозитория " + obj_id + " сохранён файл размером более 1 Гб, необходимо удалить данный объект");
Debug.WriteLine("");
ObjCounter := ObjCounter+1;
ObjList:= ObjList + " " + obj_id;
Case "OtherTables":
Debug.WriteLine("");
Debug.WriteLine("ВНИМАНИЕ: отношение " + NativeNameStr + " в схеме " + "содержит Lo-объект размером более 1
ГБ" );
Debug.WriteLine("Необходимо удалить его средствами СУБД");
Debug.WriteLine("");
ObjCounter := ObjCounter+1;
ObjList:= ObjList + " " + NativeNameStr;
End Select;
End If;
End If;
Cur_loId.Next;
End While;
Cur_loId.Close;
Com_loId.Close;
End Sub ExecDalCom;
Sub Main;
Begin
Mb := MetabaseClass.Active;
DB := MB.ItemById(DATABASE_OBJECT_ID).Open(Null) As IDatabaseInstance;
// Если объект базы данных находится под контейнейром, используйте
следующий вариант получения дескриптора объекта:
// DB:= MB.ItemByIdNamespace(DATABASE_OBJECT_ID, <ключ объекта-контейнера>).Open(Null) As IDatabaseInstance;
Com := DB.Connection.CreateCommand("");
Com.SQL := SQL_GET_LO_TABLES;
Com.Execute;
Com.Parse;
Cur := Com.CreateCursor;
Debug.WriteLine("Выполнение SQL-запроса. В некоторых случаях выполнение может занять несколько минут...");
Debug.WriteLine("");
Debug.WriteLine("Список таблиц с полями Lo в текущей схеме:");
Debug.WriteLine("-----------------------------------------:");
While Not Cur.Eof Do
CurFields := Cur.Fields;
NativeNameStr := CurFields.Item(0).Value;
FieldName := CurFields.Item(1).Value;
Debug.WriteLine(NativeNameStr + " | " + FieldName);
If FieldName = "attachment" Then
ExecDalCom("Attachments");
Else
If string.Find(NativeNameStr, ("_cb_cols_tab"), 1) > 0 Then
ExecDalCom("CubeCacheTables");
Else
If NativeNameStr = "b_mod" Then
ExecDalCom("Documents");
Else
ExecDalCom("OtherTables");
End If;
End If;
End If;
Cur.Next;
End While;
Debug.WriteLine("");
Debug.WriteLine("Проверка Lo-объектов схемы завершена");
Debug.WriteLine("------------------------------------");
If ObjCounter = 0 Then
Debug.WriteLine("Результат:");
Debug.WriteLine("Репозиторий может быть обновлён до bytea-версии");
Else
Debug.WriteLine("Результат:");
Debug.WriteLine("Найдены большие объекты (lo),
превышающие размеры для типа данных BYTEA");
Debug.WriteLine("Количество объектов: " + ObjCounter.ToString);
Debug.WriteLine("Список объектов: " + ObjList);
Debug.WriteLine("Необходимо удалить указанные объекты схемы!");
End If;
Debug.WriteLine("");
Cur.Close;
Com.Close;
End Sub Main;
В результате выполнения модуля в консоль будет выведен список таблиц со связанными объектами, размеры которых превышают 1 ГБ. Проверяемые объекты:
таблица, входящая в состав куба, в котором используется кеширование данных. Если в результате проверки обнаружена данная таблица, то снимите флажок «Кешируемый объект» в свойствах куба и удалите таблицу в навигаторе объектов или средствами СУБД;
таблица, входящая в состав справочника НСИ для хранения вложений. Если в результате проверки обнаружена данная таблица, то найдите объекты, размеры которых превышают 1 ГБ, в справочнике НСИ с помощью атрибута FILE_SIZE. После чего откройте на редактирование отчёт, к которому прикреплены найденные объекты в качестве вложений, и удалите вложения в окне «Список вложений»;
таблица, содержащая поля с типом данных «Пользовательский» или «Двоичный». Если в результате проверки обнаружена данная таблица, то удалите её средствами СУБД;
документ. Если в результате проверки обнаружен данный объект, то удалите его в навигаторе объектов.
Если объекты, размеры которых превышают 1 ГБ, не найдены, то в консоль будет выведено сообщение:
Результат:
Репозиторий может быть обновлён до BYTEA-версии
В таком случае дополнительные действия не требуются, перейдите к следующему шагу.
Для преобразования пользовательских таблиц выполните скрипт от имени пользователя postgres или администратора СУБД с привилегией SUPERUSER:
begin;
do $$
declare
r record;
col_name varchar;
begin
for r in select a.table_name, a.column_name from
information_schema.columns a,
information_schema.tables b
where a.table_schema = 'public' and -- вместо схемы 'public' укажите пользовательскую
схему при необходимости
a.table_name = b.table_name and
a.table_schema = b.table_schema and
a.udt_name='lo' and
b.table_type='BASE TABLE' and a.table_name not like 'b_%'
loop
col_name = r.column_name || '_tmp';
execute format('alter table %I add column %I bytea', r.table_name, col_name);
execute format('update %I as a set %I = lo_get(b.oid) from pg_largeobject_metadata
b where a.%I = b.oid', r.table_name, col_name, r.column_name);
execute format('select lo_unlink(oid) from pg_largeobject_metadata a, %I
b where b.%I = a.oid', r.table_name, r.column_name);
execute format('alter table %I drop column %I', r.table_name, r.column_name);
execute format('alter table %I rename column %I to %I', r.table_name, col_name,
r.column_name);
end loop;
end $$;
commit;
При возникновении ошибок, связанных с преобразованием таблиц, например, если на основе указанной таблицы создано представление сбросьте изменения и пересоздайте представление.
Если скрипт выполнен без ошибок, то пользовательские таблицы, содержащие поля с типом данных LO, будут преобразованы в таблицы, содержащие поля с типом данных BYTEA.
Для преобразования системных таблиц:
Обновите версию репозитория метаданных в менеджере репозитория: в ОС Linux, Windows.
Удалите лишние данные на сервере СУБД:
VACUUM FULL
Обновите права пользователей и групп пользователей в менеджере безопасности.
После выполнения действий системные таблицы, содержащие поля с типом данных LO, будут преобразованы в таблицы, содержащие поля с типом данных BYTEA. При преобразовании таблиц сохраняются исходные данные.
См. также: