Настройка репозитория для работы с типом данных BYTEA вместо LO

В «Форсайт. Аналитическая платформа» версии 10.9 и ниже для хранения двоичных данных в СУБД PostgreSQL используется тип данных LO. Начиная с версии 10.10, в качестве замены типа данных LO при создании новых репозиториев используется тип данных BYTEA по умолчанию.

Репозитории, созданные в предыдущих версиях продукта и использующие тип данных LO, поддерживаются без ограничений. При необходимости можно настроить репозиторий для работы с типом данных BYTEA вместо LO за счёт преобразования системных и пользовательских таблиц, заменив поля с типом данных LO на поля с типом данных BYTEA. Системные таблицы преобразовываются автоматически в процессе обновления версии репозитория средствами менеджера репозитория, пользовательские таблицы преобразовываются вручную.

Примечание. Одновременное использование типа данных LO и BYTEA в рамках одного репозитория недоступно.

Перед преобразованием системных и пользовательских таблиц выполните резервное копирование целевого репозитория и базы данных.

Для преобразования системных и пользовательских таблиц, содержащих поля с типом данных LO, в таблицы, содержащие поля с типом данных BYTEA, выполните шаги, указанные ниже.

Шаг 1. Проверка допустимого размера объектов

При использовании типа данных BYTEA размер объекта, связанного с таблицей, не должен превышать 1 ГБ. Таблицы, содержащие поля с типом данных LO, могут быть связаны с объектами размером более 1 ГБ. Для поиска таких объектов создайте в репозитории модуль с идентификатором BLOB_FINDER.

Примечание. Модуль используется в качестве примера. При необходимости используйте другие способы поиска объектов.

Для выполнения модуля задайте переменные:

Примечание. Идентификаторы схемы, указанные в параметре «Схема» на странице «Свойства базы данных» и в переменной 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(NullAs 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 ГБ, не найдены, то в консоль будет выведено сообщение:

Результат:

Репозиторий может быть обновлён до BYTEA-версии

В таком случае дополнительные действия не требуются, перейдите к следующему шагу.

Шаг 2. Преобразование пользовательских таблиц

Для преобразования пользовательских таблиц выполните скрипт от имени пользователя 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.

Шаг 3. Преобразование системных таблиц

Для преобразования системных таблиц:

  1. Обновите версию репозитория метаданных в менеджере репозитория: в ОС Linux, Windows.

  2. Удалите лишние данные на сервере СУБД:

VACUUM FULL

  1. Обновите права пользователей и групп пользователей в менеджере безопасности.

После выполнения действий системные таблицы, содержащие поля с типом данных LO, будут преобразованы в таблицы, содержащие поля с типом данных BYTEA. При преобразовании таблиц сохраняются исходные данные.

См. также:

Создание и управление репозиториями метаданных