create or replace package PKG_P8PANELS_QE_BASE as

  /* Типы данных - Аргумент */
  type TARG is record
  (
    SNAME                   PKG_STD.TSTRING, -- Имя
    STITLE                  PKG_STD.TSTRING, -- Заголовок
    NDATA_TYPE              PKG_STD.TNUMBER, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              PKG_STD.TNUMBER, -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  PKG_STD.TLSTRING -- Значение для отладки (строковое представление для всех типов данных)
  );
  
  /* Типы данных - Коллекция аргументов */
  type TARGS is table of TARG;
  
  /* Типы данных - Настройка запроса */
  type TOPT is record
  (
    RARGS                   TARGS,           -- Аргументы
    SCOND                   PKG_STD.TSTRING, -- Условия отбора
    NSUBST_ARGS_VALS        PKG_STD.TNUMBER  -- Флаг подстановки значений аргументов в запрос (1 - да, 0 - нет)
  );

  /* Типы данных - Атрибут сущности */
  type TATTR is record
  (
    SID                     PKG_STD.TSTRING, -- Уникальный идентификатор в запросе
    SPARENT_ENTITY          PKG_STD.TSTRING, -- Идентификатор родительской сущности
    SNAME                   PKG_STD.TSTRING, -- Имя
    STITLE                  PKG_STD.TSTRING, -- Заголовок
    NDATA_TYPE              PKG_STD.TNUMBER, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    SAGG                    PKG_STD.TSTRING, -- Агрегатная функция
    SALIAS                  PKG_STD.TSTRING, -- Псевдоним в выборке
    NUSE                    PKG_STD.TNUMBER, -- Флаг применения в запросе (1 - да, 0 - нет)
    NSHOW                   PKG_STD.TNUMBER  -- Флаг отображения в выборке (1 - да, 0 - нет)
  );
  
  /* Типы данных - Коллекция атрибутов сущности */
  type TATTRS is table of TATTR;

  /* Типы данных - Сущность */
  type TENT is record
  (
    SID                     PKG_STD.TSTRING,      -- Уникальный идентификатор в запросе
    SNAME                   PKG_STD.TSTRING,      -- Имя
    STITLE                  PKG_STD.TSTRING,      -- Заголовок
    STYPE                   PKG_STD.TSTRING,      -- Тип (см. константы SENT_TYPE_*)
    NX                      PKG_STD.TNUMBER := 0, -- Координата по оси абсцисс
    NY                      PKG_STD.TNUMBER := 0, -- Координата по оси ординат
    RATTRS                  TATTRS                -- Атрибуты
  );

  /* Типы данных - Коллекция сущностей */
  type TENTS is table of TENT;
  
  /* Типы данных - Отношение */
  type TRL is record
  (
    SID                     PKG_STD.TSTRING, -- Уникальный идентификатор в запросе
    SSOURCE                 PKG_STD.TSTRING, -- Идентификатор атрибута-источника
    STARGET                 PKG_STD.TSTRING, -- Идентификатор атрибута-приёмника
    NMANDATORY              PKG_STD.TNUMBER  -- Флаг обязательности (1 - да, 0 - нет)
  );
  
  /* Типы данных - Коллекция отношений */
  type TRLS is table of TRL;
  
  /* Получение заголовка представления из метаданных */
  function DMSCLVIEWS_TITLE_GET
  (
    SVIEW_NAME              in varchar2 -- Имя представления
  ) return                  varchar2;   -- Заголовок представления из метаданных
  
  /* Поиск индекса атрибута по псевдониму */
  function TATTRS_INDEX_BY_ALIAS
  (
    RATTRS                  in TATTRS,  -- Коллекция атрибутов
    SALIAS                  in varchar2 -- Искомый псевдоним
  ) return                  number;     -- Индекс найденного атрибута (null - если не найдено)

  /* Десериализация коллекции атрибутов сущности (из BASE64) */
  function TATTRS_FROM_XML_BASE64
  (
    CXML                    in clob,            -- XML-описание коллекции атрибутов сущности (BASE64)
    SCHARSET                in varchar2,        -- Кодировка, в которой XML-данные были упакованы в BASE64
    BADD_ROOT               in boolean := false -- Флаг необходимости добавления корневого тэга (false - нет, true - да)
  ) return                  TATTRS;             -- Коллекция атрибутов сущности
  
  /* Проверка корректности атрибутов сущности */
  procedure TATTRS_CHECK
  (
    RATTRS                  in TATTRS,   -- Коллекция атрибутов сущности
    SCHECK_MSG              out varchar2 -- Сообщения проверки (null - ошибок нет)
  );
  
  /* Поиск индекса аргумента по имени */
  function TARGS_INDEX_BY_NAME
  (
    RARGS                   in TARGS,   -- Коллекция аргументов
    SNAME                   in varchar2 -- Искомое имя
  ) return                  number;     -- Индекс найденного аргумента (null - если не найдено)
  
  /* Добавление аргумента в коллекцию */
  procedure TARGS_ADD
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    SNAME                   in varchar2,         -- Имя
    STITLE                  in varchar2,         -- Заголовок
    NDATA_TYPE              in number,           -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              in number,           -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  in varchar2          -- Значение для отладки (строковое представление для всех типов данных)
  );
  
  /* Изменение аргумента в коллекции */
  procedure TARGS_EDIT
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    NINDEX                  in number,           -- Индекс изменяемого аргумента
    SNAME                   in varchar2,         -- Имя
    STITLE                  in varchar2,         -- Заголовок
    NDATA_TYPE              in number,           -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              in number,           -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  in varchar2          -- Значение для отладки (строковое представление для всех типов данных)
  );
  
  /* Удаление аргумента из коллекции */
  procedure TARGS_REMOVE
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    SNAME                   in varchar2          -- Имя удаляемого аргумента
  );
  
  /* Поиск индекса сущности по идентификатору */
  function TENTS_INDEX_BY_ID
  (
    RENTS                   in TENTS,   -- Коллекция сущностей
    SID                     in varchar2 -- Искомый идентификатор
  ) return                  number;     -- Индекс найденной сущности (null - если не найдено)
  
  /* Добавление сущности в коллекцию */
  procedure TENTS_ADD
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SNAME                   in varchar2,         -- Имя
    STYPE                   in varchar2,         -- Тип (см. константы SENT_TYPE_*)
    NINIT_ATTRS             in number := 1       -- Флаг инициализации атрибутов сущности (0 - нет, 1 - да)
  );
  
  /* Удаление сущности из коллекции */
  procedure TENTS_REMOVE
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SID                     in varchar2          -- Идентификатор удялемой сущности
  );

  /* Установка координат сущности в коллекции */
  procedure TENTS_POSITION_SET
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SID                     in varchar2,         -- Идентификатор сущности
    NX                      in number,           -- Координата по оси абсцисс
    NY                      in number            -- Координата по оси ординат
  );
  
  /* Добавление связи в коллекцию */
  procedure TRLS_ADD
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SSOURCE                 in varchar2,        -- Источник
    STARGET                 in varchar2,        -- Приёмник
    NMANDATORY              in number           -- Флаг обязательности (1 - да, 0 - нет)
  );
  
  /* Удаление связи из коллекции */
  procedure TRLS_REMOVE
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SID                     in varchar2         -- Идентификатор удялемой связи
  );
  
  /* Установка признака обязательности связи в коллекции */
  procedure TRLS_MANDATORY_SET
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SID                     in varchar2,        -- Идентификатор связи
    NMANDATORY              in number           -- Флаг обязательности (1 - да, 0 - нет)
  );
  
  /* Подчистка коллекции связей по атрибуту */
  procedure TRLS_CLEANUP_BY_ATTR
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SATTR_ID                in varchar2         -- Идентификатор атрибута
  );
  
  /* Формирование списка доступных сущностей */
  function ENTITY_LIST
  (
    SSEARCH                 in varchar2, -- Поисковый запрос
    NBRIEF                  in number,   -- Флаг формирования описания сущности в кратком формате (0 - нет, 1 - да)
    NINCLUDE_ATTRS          in number    -- Флаг включения в ответ атрибутов сущности (0 - нет, 1 - да)
  ) return                  clob;        -- Список сущностей
  
  /* Считывание записи запроса */
  function QUERY_GET
  (
    NRN                     in number               -- Рег. номер запроса
  ) return                  P8PNL_QE_QUERY%rowtype; -- Запись запроса
  
  /* Получение признака возможности изменения запроса */
  function QUERY_ACCESS_SIGN_MODIFY
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  ) return                  number;     -- Признак возможности изменения запроса (0 - нет, 1 - да)
  
  /* Проверка возможности изменения запроса */
  procedure QUERY_ACCESS_MODIFY
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  );
  
  /* Получение признака возможности просмотра запроса */
  function QUERY_ACCESS_SIGN_VIEW
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  ) return                  number;     -- Признак возможности просмотра запроса (0 - нет, 1 - да)
  
  /* Проверка возможности просмотра запроса */
  procedure QUERY_ACCESS_VIEW
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  );

  /* Формирование описания запроса */
  function QUERY
  (
    NRN                     in number   -- Рег. номер запроса
  ) return                  clob;       -- XML-описание

  /* Формирование списка запросов */
  function QUERY_LIST
  (
    SUSER                   in varchar2 -- Имя пользователя
  ) return                  clob;       -- Список запросов
 
  /* Добавление запроса */
  procedure QUERY_INSERT
  (
    SCODE                   in varchar2, -- Мнемокод
    SNAME                   in varchar2, -- Наименование
    NRN                     out number   -- Рег. номер добавленного запроса
  );
  
  /* Исправление запроса */
  procedure QUERY_UPDATE
  (
    NRN                     in number,   -- Рег. номер запроса
    SCODE                   in varchar2, -- Мнемокод
    SNAME                   in varchar2  -- Наименование
  );

  /* Удаление запроса */
  procedure QUERY_DELETE
  (
    NRN                     in number   -- Рег. номер запроса
  );
  
  /* Чтение сущностей запроса */
  function QUERY_ENTS_GET
  (
    CENTS                   in clob     -- Сериализованное описание сущностей
  ) return                  TENTS;      -- Коллекция сущностей

  /* Запись сущностей запроса */
  procedure QUERY_ENTS_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    RENTS                   in TENTS    -- Коллекция сущностей
  );

  /* Получение состава атрибутов сущности */
  procedure QUERY_ENT_ATTRS_GET
  (
    NRN                     in number,   -- Рег. номер запроса
    SID                     in varchar2, -- Идентификатор сущности
    COUT                    out clob     -- Сериализованное описание атрибутов сущности
  );
  
  /* Чтение связей запроса */
  function QUERY_RLS_GET
  (
    CRLS                    in clob     -- Сериализованное описание связей
  ) return                  TRLS;       -- Коллекция связей
  
  /* Запись связей запроса */
  procedure QUERY_RLS_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    RRLS                    in TRLS     -- Коллекция связей
  );
  
  /* Установка признака "готовности" запроса */
  procedure QUERY_READY_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    NREADY                  in number   -- Флаг готовности к использованию (0 - нет, 1 - да)
  );
  
  /* Установка признака "публичности" запроса */
  procedure QUERY_PBL_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    NPBL                    in number   -- Флаг публичности (0 - приватный, 1 - публичный)
  );

  /* Чтение настройки запроса */
  function QUERY_OPT_GET
  (
    COPT                    in clob     -- Сериализованное описание настройки запроса
  ) return                  TOPT;       -- Настройка запроса

  /* Запись настройки запроса */
  procedure QUERY_OPT_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    ROPT                    in TOPT     -- Настройка запроса
  );
  
  /* Обновление SQL-выражения запроса */
  procedure QUERY_QRY_REFRESH
  (
    NRN                     in number   -- Рег. номер запроса
  );
  
  /* Формирование SQL-выражения запроса */
  procedure QUERY_QRY_BUILD
  (
    NRN                     in number,    -- Рег. номер запроса
    SQRY                    out varchar2, -- SQL-выражение
    SQRY_BND                out varchar2, -- SQL-выражение с подставленными значениями аргументов
    SQRY_MSG                out varchar2  -- Сообщение при формировании SQL-выражения (предупреждения и ошибки формирования)
  );  

end PKG_P8PANELS_QE_BASE;
/
create or replace package body PKG_P8PANELS_QE_BASE as

  /* Константы - Типы сущностей */
  SENT_TYPE_TABLE           constant PKG_STD.TSTRING := 'TABLE'; -- Таблица
  SENT_TYPE_VIEW            constant PKG_STD.TSTRING := 'VIEW';  -- Представление

  /* Константы - Теги для сериализации */
  STAG_DATA                 constant PKG_STD.TSTRING := 'XDATA';            -- Данные
  STAG_QUERIES              constant PKG_STD.TSTRING := 'XQUERIES';         -- Запросы
  STAG_QUERY                constant PKG_STD.TSTRING := 'XQUERY';           -- Запрос
  STAG_ARGS                 constant PKG_STD.TSTRING := 'XARGS';            -- Аргументы запроса
  STAG_ARG                  constant PKG_STD.TSTRING := 'XARG';             -- Аргумент запроса
  STAG_ATTRS                constant PKG_STD.TSTRING := 'XATTRS';           -- Атрибуты сущности
  STAG_ATTR                 constant PKG_STD.TSTRING := 'XATTR';            -- Атрибут сущности
  STAG_ENTS                 constant PKG_STD.TSTRING := 'XENTS';            -- Сущности
  STAG_ENT                  constant PKG_STD.TSTRING := 'XENT';             -- Сущность
  STAG_RLS                  constant PKG_STD.TSTRING := 'XRLS';             -- Связи
  STAG_RL                   constant PKG_STD.TSTRING := 'XRL';              -- Связь
  STAG_OPT                  constant PKG_STD.TSTRING := 'XOPT';             -- Настройка запроса
  STAG_COND                 constant PKG_STD.TSTRING := 'XCOND';            -- Условия запроса
  STAG_SUBST_ARGS_VALS      constant PKG_STD.TSTRING := 'XSUBST_ARGS_VALS'; -- Флаг подстановки значений аргументов в запрос
  STAG_QRY                  constant PKG_STD.TSTRING := 'XQRY';             -- SQL-выражение запроса
  STAG_QRY_BND              constant PKG_STD.TSTRING := 'XQRY_BND';         -- SQL-выражение запроса с подстановками
  STAG_QRY_MSG              constant PKG_STD.TSTRING := 'XQRY_MSG';         -- Сообщение при формировании запроса

  /* Константы - Атрибуты для сериализации */
  SATTR_ID                  constant PKG_STD.TSTRING := 'id';           -- Идентификатор
  SATTR_RN                  constant PKG_STD.TSTRING := 'rn';           -- Регистрационный номер
  SATTR_CODE                constant PKG_STD.TSTRING := 'code';         -- Код
  SATTR_NAME                constant PKG_STD.TSTRING := 'name';         -- Имя
  SATTR_AUTHOR              constant PKG_STD.TSTRING := 'author';       -- Автор
  SATTR_CH_DATE             constant PKG_STD.TSTRING := 'chDate';       -- Дата изменения
  SATTR_READY               constant PKG_STD.TSTRING := 'ready';        -- Готовность к использованию
  SATTR_PBL                 constant PKG_STD.TSTRING := 'pbl';          -- Публичность
  SATTR_MODIFY              constant PKG_STD.TSTRING := 'modify';       -- Изменяемость
  SATTR_TITLE               constant PKG_STD.TSTRING := 'title';        -- Заголовок
  SATTR_TYPE                constant PKG_STD.TSTRING := 'type';         -- Тип
  SATTR_DATA_TYPE           constant PKG_STD.TSTRING := 'dataType';     -- Тип данных
  SATTR_MANDATORY           constant PKG_STD.TSTRING := 'mandatory';    -- Обязательность
  SATTR_X                   constant PKG_STD.TSTRING := 'x';            -- Координата по X
  SATTR_Y                   constant PKG_STD.TSTRING := 'y';            -- Координата по Y
  SATTR_SOURCE              constant PKG_STD.TSTRING := 'source';       -- Источник
  SATTR_TARGET              constant PKG_STD.TSTRING := 'target';       -- Приёмник
  SATTR_AGG                 constant PKG_STD.TSTRING := 'agg';          -- Агрегатная функция
  SATTR_ALIAS               constant PKG_STD.TSTRING := 'alias';        -- Псевдоним
  SATTR_USE                 constant PKG_STD.TSTRING := 'use';          -- Применение в запросе
  SATTR_SHOW                constant PKG_STD.TSTRING := 'show';         -- Отображение в запросе
  SATTR_PARENT_ENTITY       constant PKG_STD.TSTRING := 'parentEntity'; -- Идентификатор родительской сущности
  SATTR_VALUE               constant PKG_STD.TSTRING := 'value';        -- Значение
  
  /* Константы - зарезервированные имена рагументов */
  SARG_NAME_PAGE            constant PKG_STD.TSTRING := 'NPAGE';      -- Номер страницы
  SARG_NAME_PAGE_SIZE       constant PKG_STD.TSTRING := 'NPAGE_SIZE'; -- Размер страницы (записей)
  
  /* Получение заголовка представления из метаданных */
  function DMSCLVIEWS_TITLE_GET
  (
    SVIEW_NAME              in varchar2 -- Имя представления
  ) return                  varchar2    -- Заголовок представления из метаданных
  is
  begin
    /* Обратимся к метаописанию представления */
    for V in (select T.VIEW_NOTE,
                     UL.UNITNAME
                from DMSCLVIEWS T,
                     UNITLIST   UL
               where T.VIEW_NAME = SVIEW_NAME
                 and T.CUSTOM_QUERY = 0
                 and T.PRN = UL.RN)
    loop
      if (V.VIEW_NOTE = SVIEW_NAME) then
        return V.UNITNAME;
      else
        return V.VIEW_NOTE;
      end if;
    end loop;
    /* Ничего не нашли - вернём обычное имя */
    return SVIEW_NAME;
  end DMSCLVIEWS_TITLE_GET;
  
  /* Получение заголовка атрибута представления из метаданных */
  function DMSCLVIEWSATTRS_TITLE_GET
  (
    SVIEW_NAME              in varchar2, -- Имя представления
    SATTR_NAME              in varchar2  -- Имя атрибута
  ) return                  varchar2     -- Заголовок атрибута представления из метаданных
  is
  begin
    /* Обратимся к метаописанию представления */
    for V in (select T.RN
                from DMSCLVIEWS T
               where T.VIEW_NAME = SVIEW_NAME
                 and T.CUSTOM_QUERY = 0)
    loop
      /* Обходим поля найденного представления */
      for F in (select A.CAPTION
                  from DMSCLVIEWSATTRS T,
                       DMSCLATTRS      A
                 where T.PRN = V.RN
                   and T.COLUMN_NAME = SATTR_NAME
                   and T.ATTR = A.RN)
      loop
        return F.CAPTION;
      end loop;
    end loop;
    /* Ничего не нашли - вернём обычное имя */
    return SATTR_NAME;
  end DMSCLVIEWSATTRS_TITLE_GET;
  
  /* Формирование аргумента */
  function TARG_MAKE
  (
    SNAME                   in varchar2, -- Имя
    STITLE                  in varchar2, -- Заголовок
    NDATA_TYPE              in number,   -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              in number,   -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  in varchar2  -- Значение для отладки (строковое представление для всех типов данных)
  ) return                  TARG         -- Описание аргумента
  is
    RARG                    TARG;        -- Буфер для результата
  begin
    /* Проверим имя */
    if (SNAME is null) then
      P_EXCEPTION(0, 'Имя аргумента не указано.');
    end if;
    for C in (select null from DUAL where not REGEXP_LIKE(SNAME, '^[A-z0-9_]+$'))
    loop
      P_EXCEPTION(0,
                  'Имя аргумента содержит недопустимые символы (разрешены латинские буквы, цифры и символ нижнего подчеркивания).');
    end loop;
    for C in (select null from DUAL where not REGEXP_LIKE(SUBSTR(SNAME, 1, 1), '^[A-z]+$'))
    loop
      P_EXCEPTION(0, 'Имя аргумента должно начинаться с буквы.');
    end loop;
    if (UPPER(SNAME) in (UPPER(SARG_NAME_PAGE), UPPER(SARG_NAME_PAGE_SIZE))) then
      P_EXCEPTION(0, 'Это имя аргумента зарезервировано Системой.');
    end if;
    /* Проверим заголовок */
    if (STITLE is null) then
      P_EXCEPTION(0, 'Не указан заголовок аргумента.');
    end if;
    /* Проверим корректность типа сущности */
    if (NDATA_TYPE is null) then
      P_EXCEPTION(0, 'Не указан тип данных аргумента.');
    end if;
    if (NDATA_TYPE not in (PKG_STD.DATA_TYPE_STR, PKG_STD.DATA_TYPE_NUM, PKG_STD.DATA_TYPE_DATE)) then
      P_EXCEPTION(0,
                  'Аргументы типа "%s" не поддерживаются.',
                  COALESCE(TO_CHAR(NDATA_TYPE), '<НЕ УКАЗАН>'));
    end if;
    /* Проверим корректность флага обязательности */
    if (NMANDATORY is null) then
      P_EXCEPTION(0, 'Не указан признак обязательности аргумента.');
    end if;
    if (NMANDATORY not in (0, 1)) then
      P_EXCEPTION(0,
                  'Некорректно указан признак обязательности аргумента.');
    end if;
    /* Собираем аргумент */
    RARG.SNAME      := UPPER(SNAME);
    RARG.STITLE     := STITLE;
    RARG.NDATA_TYPE := NDATA_TYPE;
    RARG.NMANDATORY := NMANDATORY;
    RARG.SVALUE     := SVALUE;
    /* Вернем полученное */
    return RARG;
  end TARG_MAKE;
  
  /* Сериализация аргумента */
  procedure TARG_TO_XML
  (
    RARG                    in TARG     -- Аргумент
  )
  is    
  begin
    /* Открываем описание аргумента */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ARG);
    /* Аргумент */
    PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RARG.SNAME);
    PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RARG.STITLE);
    PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RARG.NDATA_TYPE);
    PKG_XFAST.ATTR(SNAME => SATTR_MANDATORY, NVALUE => RARG.NMANDATORY);
    PKG_XFAST.ATTR(SNAME => SATTR_VALUE, SVALUE => RARG.SVALUE);
    /* Закрываем описание аргумента */
    PKG_XFAST.UP();
  end TARG_TO_XML;
  
  /* Десериализация аргумента */
  function TARG_FROM_XML
  (
    CXML                    in clob              -- XML-описание аргумента
  ) return                  TARG                 -- Аргумент
  is
    RRES                    TARG;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ARG);
        /* Получаем значения */
        RRES.SNAME      := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
        RRES.STITLE     := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
        RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE);
        RRES.NMANDATORY := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_MANDATORY);
        RRES.SVALUE     := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_VALUE);
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TARG_FROM_XML;
  
  /* Поиск индекса аргумента по имени */
  function TARGS_INDEX_BY_NAME
  (
    RARGS                   in TARGS,   -- Коллекция аргументов
    SNAME                   in varchar2 -- Искомое имя
  ) return                  number      -- Индекс найденного аргумента (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RARGS is not null) and (RARGS.COUNT > 0)) then
      for I in RARGS.FIRST .. RARGS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (UPPER(RARGS(I).SNAME) = UPPER(SNAME)) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TARGS_INDEX_BY_NAME;
  
  /* Добавление аргумента в коллекцию */
  procedure TARGS_ADD
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    SNAME                   in varchar2,         -- Имя
    STITLE                  in varchar2,         -- Заголовок
    NDATA_TYPE              in number,           -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              in number,           -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  in varchar2          -- Значение для отладки (строковое представление для всех типов данных)
  )
  is
    RARG                    TARG;                -- Добавляемый аргумент
  begin
    /* Инициализируем коллекцию если необходимо */
    if (RARGS is null) then
      RARGS := TARGS();
    end if;
    /* Проверим уникальность имени */
    if ((SNAME is not null) and (TARGS_INDEX_BY_NAME(RARGS => RARGS, SNAME => SNAME) is not null)) then
      P_EXCEPTION(0, 'Аргумент с именем "%s" уже существует.', UPPER(SNAME));
    end if;
    /* Формируем аргумент */
    RARG := TARG_MAKE(SNAME      => SNAME,
                      STITLE     => STITLE,
                      NDATA_TYPE => NDATA_TYPE,
                      NMANDATORY => NMANDATORY,
                      SVALUE     => SVALUE);
    /* Добавляем его в коллекцию */
    RARGS.EXTEND();
    RARGS(RARGS.LAST) := RARG;
  end TARGS_ADD;
  
  /* Изменение аргумента в коллекции */
  procedure TARGS_EDIT
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    NINDEX                  in number,           -- Индекс изменяемого аргумента
    SNAME                   in varchar2,         -- Имя
    STITLE                  in varchar2,         -- Заголовок
    NDATA_TYPE              in number,           -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
    NMANDATORY              in number,           -- Флаг обязательности (1 - да, 0 - нет)
    SVALUE                  in varchar2          -- Значение для отладки (строковое представление для всех типов данных)
  )
  is
    RARG                    TARG;                -- Добавляемый аргумент
  begin
    /* Проверим коллекцию */
    if ((RARGS is null) or (RARGS.COUNT = 0)) then
      P_EXCEPTION(0,
                  'Коллекция аргументов пуста. Изменение аргумента невозможно.');
    end if;
    /* Проверим индекс изменяемого аргумента */
    if (NINDEX is null) then
      P_EXCEPTION(0, 'Не указан индекс изменяемого аргумента.');
    end if;
    /* Формируем аргумент */
    RARG := TARG_MAKE(SNAME      => SNAME,
                      STITLE     => STITLE,
                      NDATA_TYPE => NDATA_TYPE,
                      NMANDATORY => NMANDATORY,
                      SVALUE     => SVALUE);
    /* Обновляем аргумент в коллекции */
    begin
      RARGS(NINDEX) := RARG;
    exception
      when others then
        P_EXCEPTION(0, 'Изменяемый аргумент отсутствует в коллекции.');
    end;
  end TARGS_EDIT;
  
  /* Удаление аргумента из коллекции */
  procedure TARGS_REMOVE
  (
    RARGS                   in out nocopy TARGS, -- Изменяемая коллекция
    SNAME                   in varchar2          -- Имя удаляемого аргумента
  )
  is
    NIND                    PKG_STD.TNUMBER;     -- Индекс аргумента коллекции
  begin
    /* Найдем индекс аргумента в коллекции */
    NIND := TARGS_INDEX_BY_NAME(RARGS => RARGS, SNAME => SNAME);
    /* Удалим его, если нашли */
    if (NIND is not null) then
      RARGS.DELETE(NIND);
    end if;
  end TARGS_REMOVE;  
  
  /* Сериализация коллекции аргументов */
  procedure TARGS_TO_XML
  (
    RARGS                   in TARGS    -- Коллекция аргументов
  )
  is
  begin
    /* Открываем описание аргументов */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ARGS);
    /* Обходим аргументы из коллекции */
    if ((RARGS is not null) and (RARGS.COUNT > 0)) then
      for I in RARGS.FIRST .. RARGS.LAST
      loop
        begin
          /* Добавляем описание аргумента */
          TARG_TO_XML(RARG => RARGS(I));
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Закрываем описание аргумента */
    PKG_XFAST.UP();
  end TARGS_TO_XML;
  
  /* Десериализация коллекции аргументов */
  function TARGS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции аргументов
  ) return                  TARGS                -- Коллекция аргументов
  is
    RRES                    TARGS;               -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
    XNODES                  PKG_XPATH.TNODES;    -- Буфер коллекции узлов документа
  begin
    /* Инициализируем результат */
    RRES := TARGS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считывание списка аргументов */
        XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ARGS || '/' || STAG_ARG);
        /* Цикл по списку аргументов */
        for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES)
        loop
          /* Считаем элемент по его номеру */
          XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I);
          /* Сериализуем и добавим его в коллекцию */
          RRES.EXTEND();
          RRES(RRES.LAST) := TARG_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        end loop;
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TARGS_FROM_XML;
  
  /* Сериализация настройки */
  procedure TOPT_TO_XML
  (
    ROPT                    in TOPT     -- Настройка
  )
  is    
  begin
    /* Открываем описание настройки */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_OPT);
    /* Аргументы */
    TARGS_TO_XML(RARGS => ROPT.RARGS);
    /* Условия отбора */
    PKG_XFAST.HERB(SNAME => STAG_COND, SVALUE => ROPT.SCOND);
    /* Флаг подстановки значений аргументов в запрос */
    PKG_XFAST.HERB(SNAME => STAG_SUBST_ARGS_VALS, NVALUE => ROPT.NSUBST_ARGS_VALS);
    /* Закрываем описание настройки */
    PKG_XFAST.UP();
  end TOPT_TO_XML;
  
  /* Десериализация настройки */
  function TOPT_FROM_XML
  (
    CXML                    in clob              -- XML-описание настройки
  ) return                  TOPT                 -- Настройка
  is
    RRES                    TOPT;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Инициализируем настройку */
    RRES.RARGS := TARGS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считаваем узел настройки */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_OPT);
        /* Получаем значения */
        RRES.SCOND            := PKG_XPATH.VALUE(RNODE => XNODE, SPATTERN => STAG_COND);
        RRES.NSUBST_ARGS_VALS := PKG_XPATH.VALUE_NUM(RNODE => XNODE, SPATTERN => STAG_SUBST_ARGS_VALS);
        RRES.RARGS            := TARGS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XNODE,
                                                                                                                   SPATTERN     => STAG_ARGS)));
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TOPT_FROM_XML;
  
  /* Формирование идентификатора атрибута сущности */
  function TATTR_ID_MAKE
  (
    SENT_ID                 in varchar2, -- Уникальный идентификатор родительской сущности
    SNAME                   in varchar2  -- Имя атрибута
  ) return                  varchar2     -- Сформированный идентификатор
  is
  begin
    /* Проверим параметры */
    if (SNAME is null) then
      P_EXCEPTION(0, 'Не указано имя атрибута сущности.');
    end if;
    if (SENT_ID is null) then
      P_EXCEPTION(0,
                  'Не указан идентификатор родительской сущности атрибута.');
    end if;
    /* Соберем идентификатор */
    return SENT_ID || '.' || SNAME;
  end TATTR_ID_MAKE;
  
  /* Формирование псевдонима атрибута сущности */
  function TATTR_ALIAS_MAKE
  (
    SENT_ID                 in varchar2,             -- Уникальный идентификатор родительской сущности
    RATTRS                  in TATTRS,               -- Коллекция уже существующих атрибутов
    NINDEX                  in number                -- Индекс элемента, для которого формируется псевдоним
  ) return                  varchar2                 -- Сформированный идентификатор
  is
    SRES                    PKG_STD.TSTRING;         -- Буфер для результата
    NCNT                    PKG_STD.TNUMBER := 0;    -- Текущая попытка
    NMAX_CNT                PKG_STD.TNUMBER := 1000; -- Предельное количество попыток
    NATTR_INDEX             PKG_STD.TNUMBER := 0;    -- Индекс поля в коллекции атрибутов сущности, при проверке уникальности псевдонима
  begin
    /* Подбираем псевдоним */
    while ((NATTR_INDEX is not null) and (NCNT < NMAX_CNT))
    loop
      SRES        := SENT_ID || TO_CHAR(NINDEX + NCNT);
      NATTR_INDEX := TATTRS_INDEX_BY_ALIAS(RATTRS => RATTRS, SALIAS => SRES);
      NCNT        := NCNT + 1;
    end loop;
    /* Если так и не смогли сформировать уникальный псевдоним */
    if ((NATTR_INDEX is not null) and (NCNT >= NMAX_CNT)) then
      P_EXCEPTION(0,
                  'Не удалось сформировать уникальный псевдоним для атрибута сущности "%s".',
                  SENT_ID);
    end if;
    /* Вернём полученный псевдоним */
    return SRES;
  end TATTR_ALIAS_MAKE;
  
  /* Сериализация атрибута сущности */
  procedure TATTR_TO_XML
  (
    RATTR                   in TATTR    -- Атрибут сущности
  )
  is    
  begin
    /* Открываем описание атрибута сущности */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTR);
    /* Атрибут */
    PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RATTR.SID);
    PKG_XFAST.ATTR(SNAME => SATTR_PARENT_ENTITY, SVALUE => RATTR.SPARENT_ENTITY);
    PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME);
    PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE);
    PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE);
    PKG_XFAST.ATTR(SNAME => SATTR_AGG, SVALUE => RATTR.SAGG);
    PKG_XFAST.ATTR(SNAME => SATTR_ALIAS, SVALUE => RATTR.SALIAS);
    PKG_XFAST.ATTR(SNAME => SATTR_USE, NVALUE => RATTR.NUSE);
    PKG_XFAST.ATTR(SNAME => SATTR_SHOW, NVALUE => RATTR.NSHOW);
    /* Закрываем описание атрибута сущности */
    PKG_XFAST.UP();
  end TATTR_TO_XML;
  
  /* Десериализация атрибута сущности */
  function TATTR_FROM_XML
  (
    CXML                    in clob              -- XML-описание атрибута сущности
  ) return                  TATTR                -- Атрибут сущности
  is
    RRES                    TATTR;               -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTR);
        /* Получаем значения */
        RRES.SID            := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID);
        RRES.SPARENT_ENTITY := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_PARENT_ENTITY);
        RRES.SNAME          := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
        RRES.STITLE         := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
        RRES.NDATA_TYPE     := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE);
        RRES.SAGG           := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_AGG);
        RRES.SALIAS         := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ALIAS);
        RRES.NUSE           := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_USE);
        RRES.NSHOW          := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_SHOW);
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TATTR_FROM_XML;
  
  /* Поиск индекса атрибута по идентификатору */
  function TATTRS_INDEX_BY_ID
  (
    RATTRS                  in TATTRS,  -- Коллекция атрибутов
    SID                     in varchar2 -- Искомый идентификатор
  ) return                  number      -- Индекс найденного атрибута (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (RATTRS(I).SID = SID) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TATTRS_INDEX_BY_ID;

  /* Поиск индекса атрибута по имени */
  function TATTRS_INDEX_BY_NAME
  (
    RATTRS                  in TATTRS,  -- Коллекция атрибутов
    SNAME                   in varchar2 -- Искомое имя
  ) return                  number      -- Индекс найденного атрибута (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (RATTRS(I).SNAME = SNAME) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TATTRS_INDEX_BY_NAME;
  
  /* Поиск индекса атрибута по псевдониму */
  function TATTRS_INDEX_BY_ALIAS
  (
    RATTRS                  in TATTRS,  -- Коллекция атрибутов
    SALIAS                  in varchar2 -- Искомый псевдоним
  ) return                  number      -- Индекс найденного атрибута (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (RATTRS(I).SALIAS = SALIAS) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TATTRS_INDEX_BY_ALIAS;
  
  /* Подсчет количества атрибутов по псевдониму */
  function TATTRS_COUNT_BY_ALIAS
  (
    RATTRS                  in TATTRS,            -- Коллекция атрибутов
    SALIAS                  in varchar2           -- Искомый псевдоним
  ) return                  number                -- Количество найденных атрибутов
  is
    NRES                    PKG_STD.TNUMBER := 0; --Буфер для расчета
  begin
    /* Обходим коллекцию */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        begin
          /* Увеличиваем счетчик, если наш клиент */
          if (RATTRS(I).SALIAS = SALIAS) then
            NRES := NRES + 1;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Вернем количество найденных */
    return NRES;
  end TATTRS_COUNT_BY_ALIAS;
 
  /* Формирование списка атрибутов сущности */
  function TATTRS_MAKE
  (
    RENT                    in TENT,                  -- Родительская сущность
    NCOUNT                  in number := null         -- Количество атрибутов (null - все)
  ) return                  TATTRS                    -- Коллекция атрибутов сущности
  is
    RRES                    TATTRS;                   -- Буфер для результата
    RVIEW_FIELDS            PKG_OBJECT_DESC.TCOLUMNS; -- Коллекция описаний полей представления
    RVIEW_FIELD             PKG_OBJECT_DESC.TCOLUMN;  -- Описание поля представления
    NATTR_INDEX             PKG_STD.TNUMBER;          -- Индекс поля в коллекции атрибутов сущности
    BINIT                   boolean := false;         -- Признак инициализации атрибутов сущности
  begin
    /* Проверим корректность типа сущности */
    if (RENT.STYPE is null) then
      P_EXCEPTION(0, 'Не указан тип сущности.');
    end if;
    if (RENT.STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then
      P_EXCEPTION(0,
                  'Сущности типа "%s" не поддерживаются.',
                  COALESCE(RENT.STYPE, '<НЕ УКАЗАН>'));
    end if;
    /* Проверим, что у сущности задан идентификатор */
    if (RENT.SID is null) then
      P_EXCEPTION(0,
                  'Ошибка формирования атрибутов - не задан идентификатор сущности.');
    end if;
    /* Проверим, что у сущности задано имя */
    if (RENT.SNAME is null) then
      P_EXCEPTION(0,
                  'Ошибка формирования атрибутов - не задано имя сущности.');
    end if;
    /* Инициализируем результат */
    RRES := TATTRS();
    /* Установим флаг инициализации - в сущности нет атрибутов и нас просили ограничить количество */
    if (((RENT.RATTRS is null) or (RENT.RATTRS.COUNT = 0)) and (NCOUNT is not null)) then
      BINIT := true;
    end if;
    /* Если сущность это представление */
    if (RENT.STYPE = SENT_TYPE_VIEW) then
      /* Получим список полей представления */
      RVIEW_FIELDS := PKG_OBJECT_DESC.DESC_SEL_COLUMNS(SSELECT_NAME => RENT.SNAME, BRAISE_ERROR => true);
      /* Собираем атрибуты в ответ */
      for I in 1 .. PKG_OBJECT_DESC.COUNT_COLUMNS(RCOLUMNS => RVIEW_FIELDS)
      loop
        /* Считываем очередное поле из коллекции описания полей представления */
        RVIEW_FIELD := PKG_OBJECT_DESC.FETCH_COLUMN(RCOLUMNS => RVIEW_FIELDS, IINDEX => I);
        /* Если поле поддерживаемого типа */
        if (RVIEW_FIELD.DATA_TYPE in (PKG_STD.DATA_TYPE_STR(), PKG_STD.DATA_TYPE_NUM(), PKG_STD.DATA_TYPE_DATE())) then
          /* Добавляем элемент в результирующую коллекцию */
          RRES.EXTEND();
          /* Ищем такой атрибут в родительской сущности */
          NATTR_INDEX := TATTRS_INDEX_BY_NAME(RATTRS => RENT.RATTRS, SNAME => RVIEW_FIELD.COLUMN_NAME);
          /* Если это поле уже есть в коллекции атрибутов родительской сущности */
          if (NATTR_INDEX is not null) then
            /* Возьмём элемент оттуда */
            RRES(RRES.LAST) := RENT.RATTRS(NATTR_INDEX);
          else
            /* Такого элемента нет - берем из представления */
            RRES(RRES.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME);
            RRES(RRES.LAST).SPARENT_ENTITY := RENT.SID;
            RRES(RRES.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME;
            RRES(RRES.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME,
                                                                SATTR_NAME => RRES(RRES.LAST).SNAME);
            RRES(RRES.LAST).NDATA_TYPE := RVIEW_FIELD.DATA_TYPE;
            RRES(RRES.LAST).SALIAS := TATTR_ALIAS_MAKE(SENT_ID => RENT.SID, RATTRS => RRES, NINDEX => RRES.LAST);
            /* Если это инициализиция сущности - установим флаг применения в запросе (тогда атрибут будет отображен в диаграмме) */
            if (BINIT) then
              RRES(RRES.LAST).NUSE := 1;
              RRES(RRES.LAST).NSHOW := 1;
            else
              RRES(RRES.LAST).NUSE := 0;
              RRES(RRES.LAST).NSHOW := 0;
            end if;
          end if;
          /* Ограничим объем коллекции если необходимо */
          if (NCOUNT is not null) then
            exit when RRES.LAST = NCOUNT;
          end if;
        end if;
      end loop;
    end if;
    /* Если сущность это таблица */
    if (RENT.STYPE = SENT_TYPE_TABLE) then
      P_EXCEPTION(0,
                  'Поддержка формирования атрибутов для сущностей типа "Таблица" ещё не реализована.');
    end if;
    /* Возвращаем результат */
    return RRES;
  end TATTRS_MAKE;

  /* Сериализация коллекции атрибутов сущности */
  procedure TATTRS_TO_XML
  (
    RATTRS                  in TATTRS   -- Коллекция атрибутов сущности
  )
  is
  begin
    /* Открываем описание атрибутов сущности */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTRS);
    /* Обходим атрибуты из коллекции */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        /* Добавляем описание атрибута сущности */
        TATTR_TO_XML(RATTR => RATTRS(I));
      end loop;
    end if;
    /* Закрываем описание атрибутов сущности */
    PKG_XFAST.UP();
  end TATTRS_TO_XML;
  
  /* Десериализация коллекции атрибутов сущности */
  function TATTRS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции атрибутов сущности
  ) return                  TATTRS               -- Коллекция атрибутов сущности
  is
    RRES                    TATTRS;              -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
    XNODES                  PKG_XPATH.TNODES;    -- Буфер коллекции узлов документа
  begin
    /* Инициализируем результат */
    RRES := TATTRS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считывание списка атрибутов */
        XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTRS || '/' || STAG_ATTR);
        /* Цикл по списку атрибутов */
        for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES)
        loop
          /* Считаем элемент по его номеру */
          XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I);
          /* Сериализуем и добавим его в коллекцию */
          RRES.EXTEND();
          RRES(RRES.LAST) := TATTR_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        end loop;
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TATTRS_FROM_XML;
  
  /* Десериализация коллекции атрибутов сущности (из BASE64) */
  function TATTRS_FROM_XML_BASE64
  (
    CXML                    in clob,            -- XML-описание коллекции атрибутов сущности (BASE64)
    SCHARSET                in varchar2,        -- Кодировка, в которой XML-данные были упакованы в BASE64
    BADD_ROOT               in boolean := false -- Флаг необходимости добавления корневого тэга (false - нет, true - да)
  ) return                  TATTRS              -- Коллекция атрибутов сущности
  is
    CTMP                    clob;               -- Буфер для преобразований
  begin
    /* Избавимся от BASE64 */
    CTMP := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CXML), SCHARSET => SCHARSET);
    /* Если надо - добавим корень */
    if (BADD_ROOT) then
      CTMP := '<' || STAG_ATTRS || '>' || CTMP || '</' || STAG_ATTRS || '>';
    end if;
    /* Десериализуем */
    return TATTRS_FROM_XML(CXML => CTMP);
  end TATTRS_FROM_XML_BASE64;
  
  /* Проверка корректности атрибутов сущности */
  procedure TATTRS_CHECK
  (
    RATTRS                  in TATTRS,   -- Коллекция атрибутов сущности
    SCHECK_MSG              out varchar2 -- Сообщения проверки (null - ошибок нет)
  )
  is
  begin
    /* Обходим коллекцию */
    if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
      for I in RATTRS.FIRST .. RATTRS.LAST
      loop
        /* Псевдоним должен быть задан */
        if (RATTRS(I).SALIAS is null) then
          SCHECK_MSG := SCHECK_MSG || 'Для атрибута "' || RATTRS(I).SNAME || '" не задан псевдоним; ';
        else
          /* А если задан - должен соответствовать по длине */
          if (LENGTH(RATTRS(I).SALIAS) > 30) then
            SCHECK_MSG := SCHECK_MSG || 'Длина псевдонима атрибута "' || RATTRS(I).SNAME ||
                          '" превышает максимально допустимую (30 символов); ';
          end if;
          /* Набору символов */
          if (INSTR(RATTRS(I).SALIAS, '"') <> 0) then
            SCHECK_MSG := SCHECK_MSG || 'Псевдоним атрибута "' || RATTRS(I).SNAME ||
                          '" содержит недопустимые символы; ';
          end if;
          /* Быть уникальным */
          if (TATTRS_COUNT_BY_ALIAS(RATTRS => RATTRS, SALIAS => RATTRS(I).SALIAS) > 1) then
            SCHECK_MSG := SCHECK_MSG || 'Нарушение уникальности псевдонима атрибута "' || RATTRS(I).SNAME || '"; ';
          end if;
        end if;
      end loop;
    end if;
    if (SCHECK_MSG is not null) then
      SCHECK_MSG := RTRIM(SCHECK_MSG, '; ');
    end if;
  end TATTRS_CHECK;
  
  /* Формирование идентификатора сущности */
  function TENT_ID_MAKE
  (
    STYPE                   in varchar2, -- Тип (см. константы SENT_TYPE_*)
    NNUMB                   in number    -- Номер сущности в запросе
  ) return                  varchar2     -- Сформированный идентификатор
  is
  begin
    /* Проверим параметры */
    if (STYPE is null) then
      P_EXCEPTION(0, 'Не указан тип сущности.');
    end if;
    if (NNUMB is null) then
      P_EXCEPTION(0, 'Не указан номер сущности в запросе.');
    end if;
    /* Соберем идентификатор */
    return SUBSTR(STYPE, 1, 1) || TO_CHAR(NNUMB);
  end TENT_ID_MAKE;
  
  /* Формирование описания сущности */
  function TENT_MAKE
  (
    SNAME                   in varchar2,           -- Имя
    STYPE                   in varchar2,           -- Тип (см. константы SENT_TYPE_*)
    NNUMB                   in number,             -- Номер сущности
    NINIT_ATTRS             in number := 1         -- Флаг инициализации атрибутов сущности (0 - нет, 1 - да)
  ) return                  TENT                   -- Описание сущности
  is
    RENT                    TENT;                  -- Буфер для результата
    RVIEW                   PKG_OBJECT_DESC.TVIEW; -- Описание представления
  begin
    /* Проверим корректность типа сущности */
    if (STYPE is null) then
      P_EXCEPTION(0, 'Не указан тип сущности.');
    end if;
    if (STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then
      P_EXCEPTION(0,
                  'Сущности типа "%s" не поддерживаются.',
                  COALESCE(STYPE, '<НЕ УКАЗАН>'));
    end if;
    /* Если сущность это представление */
    if (STYPE = SENT_TYPE_VIEW) then
      /* Получим описание представления */
      RVIEW := PKG_OBJECT_DESC.DESC_VIEW(SVIEW_NAME => SNAME, BRAISE_ERROR => true);
      /* Собираем заголовок сущности */
      RENT.SID    := TENT_ID_MAKE(STYPE => STYPE, NNUMB => NNUMB);
      RENT.SNAME  := RVIEW.VIEW_NAME;
      RENT.STITLE := DMSCLVIEWS_TITLE_GET(SVIEW_NAME => RENT.SNAME);
      RENT.STYPE  := SENT_TYPE_VIEW;
      /* Формируем набор атрибутов */
      if (NINIT_ATTRS = 1) then
        RENT.RATTRS := TATTRS_MAKE(RENT => RENT, NCOUNT => 10);
      else
        RENT.RATTRS := TATTRS();
      end if;
    end if;
    /* Если сущность это таблица */
    if (STYPE = SENT_TYPE_TABLE) then
      P_EXCEPTION(0,
                  'Поддержка сущностей типа "Таблица" ещё не реализована.');
    end if;
    /* Вернем полученное */
    return RENT;
  end TENT_MAKE;
  
  /* Сериализация сущности */
  procedure TENT_TO_XML
  (
    RENT                    in TENT,        -- Сущность
    NBRIEF                  in number := 0, -- Флаг формирования описания сущности в кратком формате (0 - нет, 1 - да)
    NINCLUDE_ATTRS          in number := 1  -- Флаг включения атрибутов в описание (0 - нет, 1 - да)
  )
  is    
  begin
    /* Открываем описание сущности */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ENT);
    /* Cущность */
    if (NBRIEF = 0) then
      PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RENT.SID);
      PKG_XFAST.ATTR(SNAME => SATTR_X, NVALUE => RENT.NX);
      PKG_XFAST.ATTR(SNAME => SATTR_Y, NVALUE => RENT.NY);
    end if;
    PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RENT.SNAME);
    PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RENT.STITLE);
    PKG_XFAST.ATTR(SNAME => SATTR_TYPE, SVALUE => RENT.STYPE);
    /* Атрибуты */
    if (NINCLUDE_ATTRS = 1) then
      TATTRS_TO_XML(RATTRS => RENT.RATTRS);
    end if;
    /* Закрываем описание сущности */
    PKG_XFAST.UP();
  end TENT_TO_XML;
  
  /* Десериализация сущности */
  function TENT_FROM_XML
  (
    CXML                    in clob              -- XML-описание сущности
  ) return                  TENT                 -- Сущность
  is
    RRES                    TENT;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Инициализируем сущность */
    RRES.RATTRS := TATTRS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считаваем узел сущности */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENT);
        /* Получаем значения */
        RRES.SID    := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID);
        RRES.SNAME  := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
        RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
        RRES.STYPE  := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TYPE);
        RRES.NX     := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_X);
        RRES.NY     := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_Y);
        RRES.RATTRS := TATTRS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XNODE,
                                                                                                          SPATTERN     => STAG_ATTRS)));
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TENT_FROM_XML;
  
  /* Поиск индекса сущности по идентификатору */
  function TENTS_INDEX_BY_ID
  (
    RENTS                   in TENTS,   -- Коллекция сущностей
    SID                     in varchar2 -- Искомый идентификатор
  ) return                  number      -- Индекс найденной сущности (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RENTS is not null) and (RENTS.COUNT > 0)) then
      for I in RENTS.FIRST .. RENTS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (RENTS(I).SID = SID) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TENTS_INDEX_BY_ID;
  
  /* Поиск индекса сущности по идентификатору атрибута */
  function TENTS_INDEX_BY_ATTR_ID
  (
    RENTS                   in TENTS,   -- Коллекция сущностей
    SATTR_ID                in varchar2 -- Искомый идентификатор атрибута
  ) return                  number      -- Индекс найденной сущности (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RENTS is not null) and (RENTS.COUNT > 0)) then
      for I in RENTS.FIRST .. RENTS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (TATTRS_INDEX_BY_ID(RATTRS => RENTS(I).RATTRS, SID => SATTR_ID) is not null) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TENTS_INDEX_BY_ATTR_ID;
  
  /* Поиск номера сущности в коллекции */
  function TENTS_NEXT_NUMB
  (
    RENTS                   in TENTS,             -- Коллекция сущностей
    STYPE                   in varchar2           -- Тип (см. константы SENT_TYPE_*)
  ) return                  number                -- Номер сущности в коллекции
  is
    NNUMB                   PKG_STD.TNUMBER := 0; -- Буфер для результата
  begin
    /* Подбираем первый свободный номер */
    while (TENTS_INDEX_BY_ID(RENTS => RENTS, SID => TENT_ID_MAKE(STYPE => STYPE, NNUMB => NNUMB)) is not null)
    loop
      NNUMB := NNUMB + 1;
    end loop;
    /* Возвращаем его */
    return NNUMB;
  end TENTS_NEXT_NUMB;
  
  /* Добавление сущности в коллекцию */
  procedure TENTS_ADD
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SNAME                   in varchar2,         -- Имя
    STYPE                   in varchar2,         -- Тип (см. константы SENT_TYPE_*)
    NINIT_ATTRS             in number := 1       -- Флаг инициализации атрибутов сущности (0 - нет, 1 - да)
  )
  is
    RENT                    TENT;                -- Добавляемая сущность
  begin
    /* Инициализируем коллекцию если необходимо */
    if (RENTS is null) then
      RENTS := TENTS();
    end if;
    /* Формируем пописание сущности */
    RENT := TENT_MAKE(SNAME       => SNAME,
                      STYPE       => STYPE,
                      NNUMB       => TENTS_NEXT_NUMB(RENTS => RENTS, STYPE => STYPE),
                      NINIT_ATTRS => NINIT_ATTRS);
    /* Добавляем её в коллекцию */
    RENTS.EXTEND();
    RENTS(RENTS.LAST) := RENT;
  end TENTS_ADD;
  
  /* Удаление сущности из коллекции */
  procedure TENTS_REMOVE
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SID                     in varchar2          -- Идентификатор удялемой сущности
  )
  is
    NIND                    PKG_STD.TNUMBER;     -- Индекс сущности в коллекции
  begin
    /* Найдем индекс сущности в коллекции */
    NIND := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
    /* Удалим её, если нашли */
    if (NIND is not null) then
      RENTS.DELETE(NIND);
    end if;
  end TENTS_REMOVE;
  
  /* Установка координат сущности в коллекции */
  procedure TENTS_POSITION_SET
  (
    RENTS                   in out nocopy TENTS, -- Изменяемая коллекция
    SID                     in varchar2,         -- Идентификатор сущности
    NX                      in number,           -- Координата по оси абсцисс
    NY                      in number            -- Координата по оси ординат
  )
  is
    NIND                    PKG_STD.TNUMBER;     -- Индекс сущности в коллекции
  begin
    NIND := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
    if (NIND is not null) then
      RENTS(NIND).NX := NX;
      RENTS(NIND).NY := NY;
    end if;
  end TENTS_POSITION_SET;
  
  /* Формирование списка сущностей */
  function TENTS_MAKE 
  (
    SSEARCH                 in varchar2,     -- Поисковый запрос
    NINIT_ATTRS             in number        -- Флаг инициализации атрибутов сущности (0 - нет, 1 - да)
  ) return                  TENTS            -- Коллекция сущностей
  is
    RRES                    TENTS;           -- Буфер для результата
    SSEARCH_                PKG_STD.TSTRING; -- Строка поиска, предобразованная для применения в запросе
  begin
    /* Инициализируем результат */
    RRES := TENTS();
    /* Формируем список представлений */
    PKG_OBJECT_DESC.SNAP_VIEWS(NACCESS_TYPE => 1);
    /* Подготовим поисковую фразу */
    if (SSEARCH is not null) then
      SSEARCH_ := '%' || LOWER(replace(SSEARCH, ' ', '%')) || '%';
    else
      SSEARCH_ := null;
    end if;
    /* Обходим полученный список */
    for C in (select T.VIEW_NAME
                from SNAP_VIEWS T
               where ((SSEARCH_ is null) or ((SSEARCH_ is not null) and ((LOWER(T.VIEW_NAME) like SSEARCH_) or (LOWER(DMSCLVIEWS_TITLE_GET(T.VIEW_NAME)) like
                     SSEARCH_)))))
    loop
      /* Добавляем представления в коллекцию */
      TENTS_ADD(RENTS => RRES, SNAME => C.VIEW_NAME, STYPE => SENT_TYPE_VIEW, NINIT_ATTRS => NINIT_ATTRS);
    end loop;
    /* Вернём полученный список */
    return RRES;
  end TENTS_MAKE;
  
  /* Сериализация коллекции сущностей */
  procedure TENTS_TO_XML
  (
    RENTS                   in TENTS,       -- Коллекция сущностей
    NBRIEF                  in number := 0, -- Флаг формирования описания сущности в кратком формате (0 - нет, 1 - да)
    NINCLUDE_ATTRS          in number := 1  -- Флаг включения атрибутов в описание (0 - нет, 1 - да)
  )
  is
  begin
    /* Открываем описание сущностей */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_ENTS);
    /* Обходим сущности из коллекции */
    if ((RENTS is not null) and (RENTS.COUNT > 0)) then
      for I in RENTS.FIRST .. RENTS.LAST
      loop
        begin
          /* Добавляем описание сущности */
          TENT_TO_XML(RENT => RENTS(I), NBRIEF => NBRIEF, NINCLUDE_ATTRS => NINCLUDE_ATTRS);
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Закрываем описание сущностей */
    PKG_XFAST.UP();
  end TENTS_TO_XML;
  
  /* Десериализация коллекции сущностей */
  function TENTS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции сущностей
  ) return                  TENTS                -- Коллекция сущностей
  is
    RRES                    TENTS;               -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
    XNODES                  PKG_XPATH.TNODES;    -- Буфер коллекции узлов документа
  begin
    /* Инициализируем результат */
    RRES := TENTS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считывание списка сущностей */
        XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENTS || '/' || STAG_ENT);
        /* Цикл по списку сущностей */
        for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES)
        loop
          /* Считаем элемент по его номеру */
          XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I);
          /* Сериализуем и добавим его в коллекцию */
          RRES.EXTEND();
          RRES(RRES.LAST) := TENT_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        end loop;
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TENTS_FROM_XML;
  
  /* Формирование идентификатора связи */
  function TRL_ID_MAKE
  (
    SSOURCE                 in varchar2, -- Источник
    STARGET                 in varchar2  -- Приёмник
  ) return                  varchar2     -- Сформированный идентификатор
  is
  begin
    /* Проверим параметры */
    if (SSOURCE is null) then
      P_EXCEPTION(0, 'Не указан источник связи.');
    end if;
    if (STARGET is null) then
      P_EXCEPTION(0, 'Не указан приёмник связи.');
    end if;
    /* Соберем идентификатор */
    return SSOURCE || '-' || STARGET;
  end TRL_ID_MAKE;
  
  /* Формирование описания связи */
  function TRL_MAKE
  (
    SID                     in varchar2 := null, -- Идентификатор (null - автоформирование)
    SSOURCE                 in varchar2,         -- Источник
    STARGET                 in varchar2,         -- Приёмник
    NMANDATORY              in number            -- Флаг обязательности (1 - да, 0 - нет)
  ) return                  TRL                  -- Описание связи
  is
    RRL                     TRL;                 -- Буфер для результата
  begin
    /* Собираем описание связи */
    RRL.SID        := COALESCE(SID, TRL_ID_MAKE(SSOURCE => SSOURCE, STARGET => STARGET));
    RRL.SSOURCE    := SSOURCE;
    RRL.STARGET    := STARGET;
    RRL.NMANDATORY := NMANDATORY;
    /* Вернем полученное */
    return RRL;
  end TRL_MAKE;
  
  /* Сериализация связи */
  procedure TRL_TO_XML
  (
    RRL                     in TRL      -- Связь
  )
  is    
  begin
    /* Открываем описание связи */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_RL);
    /* Связь */
    PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RRL.SID);
    PKG_XFAST.ATTR(SNAME => SATTR_SOURCE, SVALUE => RRL.SSOURCE);
    PKG_XFAST.ATTR(SNAME => SATTR_TARGET, SVALUE => RRL.STARGET);
    PKG_XFAST.ATTR(SNAME => SATTR_MANDATORY, NVALUE => RRL.NMANDATORY);
    /* Закрываем описание связи */
    PKG_XFAST.UP();
  end TRL_TO_XML;
  
  /* Десериализация связи */
  function TRL_FROM_XML
  (
    CXML                    in clob              -- XML-описание связи
  ) return                  TRL                  -- Связь
  is
    RRES                    TRL;                 -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считаваем узел связи */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RL);
        /* Получаем значения */
        RRES.SID        := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID);
        RRES.SSOURCE    := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_SOURCE);
        RRES.STARGET    := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TARGET);
        RRES.NMANDATORY := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_MANDATORY);
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TRL_FROM_XML;
  
  /* Поиск индекса связи по идентификатору */
  function TRLS_INDEX_BY_ID
  (
    RRLS                    in TRLS,    -- Коллекция связей
    SID                     in varchar2 -- Искомый идентификатор
  ) return                  number      -- Индекс найденной связи (null - если не найдено)
  is
  begin
    /* Обходим коллекцию */
    if ((RRLS is not null) and (RRLS.COUNT > 0)) then
      for I in RRLS.FIRST .. RRLS.LAST
      loop
        begin
          /* Возвращаем найденный индекс */
          if (RRLS(I).SID = SID) then
            return I;
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Ничего не нашли */
    return null;
  end TRLS_INDEX_BY_ID;
    
  /* Формирование коллекции связей по источнику/приёмнику */
  function TRLS_LIST_BY_ST
  (
    RRLS                    in TRLS,     -- Коллекция связей
    SSOURCE_TARGET          in varchar2, -- Идентификатор источника/приёмкника
    NLIST_TYPE              in number    -- Тип формирования коллекции (0 - по источнику, 1 - по приёмнику
  ) return                  TRLS         -- Сформированная коллекция
  is
    RRES                    TRLS;        -- Буфер для результата
  begin
    /* Инициализируем результат */
    RRES := TRLS();
    /* Обходим входную коллекцию */
    if ((RRLS is not null) and (RRLS.COUNT > 0)) then
      for I in RRLS.FIRST .. RRLS.LAST
      loop
        begin
          /* Формируем выходную коллекцию */
          if (((NLIST_TYPE = 0) and (RRLS(I).SSOURCE = SSOURCE_TARGET)) or
             ((NLIST_TYPE = 1) and (RRLS(I).STARGET = SSOURCE_TARGET))) then
            RRES.EXTEND();
            RRES(RRES.LAST) := TRL_MAKE(SID        => RRLS(I).SID,
                                        SSOURCE    => RRLS(I).SSOURCE,
                                        STARGET    => RRLS(I).STARGET,
                                        NMANDATORY => RRLS(I).NMANDATORY);
          end if;
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Вернем результат */
    return RRES;
  end TRLS_LIST_BY_ST;
  
  /* Добавление связи в коллекцию */
  procedure TRLS_ADD
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SSOURCE                 in varchar2,        -- Источник
    STARGET                 in varchar2,        -- Приёмник
    NMANDATORY              in number           -- Флаг обязательности (1 - да, 0 - нет)
  )
  is
    RRL                     TRL;                -- Добавляемая связь
  begin
    /* Инициализируем коллекцию если необходимо */
    if (RRLS is null) then
      RRLS := TRLS();
    end if;
    /* Формируем пописание связи */
    RRL := TRL_MAKE(SSOURCE => SSOURCE, STARGET => STARGET, NMANDATORY => NMANDATORY);
    /* Добавляем её в коллекцию */
    RRLS.EXTEND();
    RRLS(RRLS.LAST) := RRL;
  end TRLS_ADD;
  
  /* Удаление связи из коллекции */
  procedure TRLS_REMOVE
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SID                     in varchar2         -- Идентификатор удялемой связи
  )
  is
    NIND                    PKG_STD.TNUMBER;    -- Индекс связи в коллекции
  begin
    /* Найдем индекс связи в коллекции */
    NIND := TRLS_INDEX_BY_ID(RRLS => RRLS, SID => SID);
    /* Удалим её, если нашли */
    if (NIND is not null) then
      RRLS.DELETE(NIND);
    end if;
  end TRLS_REMOVE;
  
  /* Установка признака обязательности связи в коллекции */
  procedure TRLS_MANDATORY_SET
  (
    RRLS                    in out nocopy TRLS, -- Изменяемая коллекция
    SID                     in varchar2,        -- Идентификатор связи
    NMANDATORY              in number           -- Флаг обязательности (1 - да, 0 - нет)
  )
  is
    NIND                    PKG_STD.TNUMBER;     -- Индекс связи в коллекции
  begin
    NIND := TRLS_INDEX_BY_ID(RRLS => RRLS, SID => SID);
    if (NIND is not null) then
      RRLS(NIND).NMANDATORY := NMANDATORY;
    end if;
  end TRLS_MANDATORY_SET;
  
  /* Подчистка коллекции связей по атрибуту */
  procedure TRLS_CLEANUP_BY_ATTR
  (
    RRLS                    in out nocopy TRLS,        -- Изменяемая коллекция
    SATTR_ID                in varchar2                -- Идентификатор атрибута
  )
  is
    RRLS_TMP                PKG_P8PANELS_QE_BASE.TRLS; -- Буфер для коллекции удаляемых связей
  begin
    /* Если атрибут есть в связях (как источник или как приёмник) */
    for J in 0 .. 1
    loop
      RRLS_TMP := TRLS_LIST_BY_ST(RRLS => RRLS, SSOURCE_TARGET => SATTR_ID, NLIST_TYPE => J);
      /* То связь должна быть удалена */
      if ((RRLS_TMP is not null) and (RRLS_TMP.COUNT > 0)) then
        for K in RRLS_TMP.FIRST .. RRLS_TMP.LAST
        loop
          TRLS_REMOVE(RRLS => RRLS, SID => RRLS_TMP(K).SID);
        end loop;
      end if;
    end loop;
  end TRLS_CLEANUP_BY_ATTR;
  
  /* Сериализация коллекции связей */
  procedure TRLS_TO_XML
  (
    RRLS                    in TRLS     -- Коллекция связей
  )
  is
  begin
    /* Открываем описание связей */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_RLS);
    /* Обходим связи из коллекции */
    if ((RRLS is not null) and (RRLS.COUNT > 0)) then
      for I in RRLS.FIRST .. RRLS.LAST
      loop
        begin
          /* Добавляем описание связи */
          TRL_TO_XML(RRL => RRLS(I));
        exception
          when NO_DATA_FOUND then
            null;
        end;
      end loop;
    end if;
    /* Закрываем описание связей */
    PKG_XFAST.UP();
  end TRLS_TO_XML;
  
  /* Десериализация коллекции связей */
  function TRLS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции связей
  ) return                  TRLS                 -- Коллекция связей
  is
    RRES                    TRLS;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
    XNODES                  PKG_XPATH.TNODES;    -- Буфер коллекции узлов документа
  begin
    /* Инициализируем результат */
    RRES := TRLS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считывание списка связей */
        XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RLS || '/' || STAG_RL);
        /* Цикл по списку связей */
        for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES)
        loop
          /* Считаем элемент по его номеру */
          XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I);
          /* Сериализуем и добавим его в коллекцию */
          RRES.EXTEND();
          RRES(RRES.LAST) := TRL_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        end loop;
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём результат */
    return RRES;
  end TRLS_FROM_XML;
  
  /* Формирование списка доступных сущностей */
  function ENTITY_LIST
  (
    SSEARCH                 in varchar2, -- Поисковый запрос
    NBRIEF                  in number,   -- Флаг формирования описания сущности в кратком формате (0 - нет, 1 - да)
    NINCLUDE_ATTRS          in number    -- Флаг включения в ответ атрибутов сущности (0 - нет, 1 - да)
  ) return                  clob         -- Список сущностей
  is
    CRES                    clob;        -- Буфер для сериализации
  begin
    /* Начинаем формирование XML */
    PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
    /* Открываем корень */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
    /* Строим и сериализуем список доступных сущностей */
    TENTS_TO_XML(RENTS          => TENTS_MAKE(SSEARCH => SSEARCH, NINIT_ATTRS => NINCLUDE_ATTRS),
                 NBRIEF         => NBRIEF,
                 NINCLUDE_ATTRS => NINCLUDE_ATTRS);
    /* Закрываем корень */
    PKG_XFAST.UP();
    /* Сериализуем */
    CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
    /* Завершаем формирование XML */
    PKG_XFAST.EPILOGUE();
    /* Возвращаем результат */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end ENTITY_LIST;
  
  /* Считывание записи запроса */
  function QUERY_GET
  (
    NRN                     in number               -- Рег. номер запроса
  ) return                  P8PNL_QE_QUERY%rowtype  -- Запись запроса
  is
    RRES                    P8PNL_QE_QUERY%rowtype; -- Буфер для результата
  begin
    select T.* into RRES from P8PNL_QE_QUERY T where T.RN = NRN;
    return RRES;
  exception
    when NO_DATA_FOUND then
      PKG_MSG.RECORD_NOT_FOUND(NFLAG_SMART => 0, NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
  end QUERY_GET;
  
  /* Получение признака возможности изменения запроса */
  function QUERY_ACCESS_SIGN_MODIFY
  (
    NRN                     in number,              -- Рег. номер запроса
    SUSER                   in varchar2             -- Имя пользователя
  ) return                  number                  -- Признак возможности изменения запроса (0 - нет, 1 - да)
  is
    RQ                      P8PNL_QE_QUERY%rowtype; -- Проверяемая запись запроса
  begin
    /* Читаем запрос */
    RQ := QUERY_GET(NRN => NRN);
    /* Менять можно только свой запрос */
    if (RQ.AUTHOR = SUSER) then
      return 1;
    end if;
    /* Проверки не пройдены - менять нельзя */
    return 0;
  end QUERY_ACCESS_SIGN_MODIFY;
  
  /* Проверка возможности изменения запроса */
  procedure QUERY_ACCESS_MODIFY
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  )
  is
  begin
    /* Получим признак возможности измнения */
    if (QUERY_ACCESS_SIGN_MODIFY(NRN => NRN, SUSER => SUSER) = 0) then
      /* Менять нельзя */
      P_EXCEPTION(0, 'У Вас нет прав доступа для измнения запроса.');
    end if;
  end QUERY_ACCESS_MODIFY;
  
  /* Получение признака возможности просмотра запроса */
  function QUERY_ACCESS_SIGN_VIEW
  (
    NRN                     in number,              -- Рег. номер запроса
    SUSER                   in varchar2             -- Имя пользователя
  ) return                  number                  -- Признак возможности просмотра запроса (0 - нет, 1 - да)
  is
    RQ                      P8PNL_QE_QUERY%rowtype; -- Проверяемая запись запроса
  begin
    /* Читаем запрос */
    RQ := QUERY_GET(NRN => NRN);
    /* Смотреть можно только свой или публичный запрос */
    if ((RQ.PBL = 1) or (RQ.AUTHOR = SUSER)) then
      return 1;
    end if;
    /* Проверки не пройдены - нельзя смотреть */
    return 0;
  end QUERY_ACCESS_SIGN_VIEW;
  
  /* Проверка возможности просмотра запроса */
  procedure QUERY_ACCESS_VIEW
  (
    NRN                     in number,  -- Рег. номер запроса
    SUSER                   in varchar2 -- Имя пользователя
  )
  is
  begin
    /* Получим признак возможности просмотра */
    if (QUERY_ACCESS_SIGN_VIEW(NRN => NRN, SUSER => SUSER) = 0) then
      /* Смотреть нельзя */
      P_EXCEPTION(0, 'У Вас нет прав доступа для просмотра запроса.');
    end if;
  end QUERY_ACCESS_VIEW;
  
  /* Проверка атрибутов запроса */
  procedure QUERY_CHECK
  (
    SCODE                   in varchar2, -- Мнемокод
    SNAME                   in varchar2  -- Наименование
  )
  is
  begin
    /* Мнемокод должен быть задан */
    if (SCODE is null) then
      P_EXCEPTION(0, 'Не задан мнемокод запроса.');
    end if;
    /* Наименование должно быть задано */
    if (SNAME is null) then
      P_EXCEPTION(0, 'Не задано наименование запроса.');
    end if;
  end QUERY_CHECK;
  
  /* Синхронизация даты изменения запроса с текущим временем */
  procedure QUERY_CH_DATE_SYNC
  (
    NRN                     in number   -- Рег. номер запроса
  )
  is
  begin
    /* Установим текущую дату изменения */
    update P8PNL_QE_QUERY T set T.CH_DATE = sysdate where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
  end QUERY_CH_DATE_SYNC;
  
  /* Сериализация сущностей запроса */
  function QUERY_ENTS_TO_XML
  (
    RENTS                   in TENTS    -- Коллекция сущностей
  ) return                  clob        -- XML-описание
  is
    CRES                    clob;       -- Буфер для результата
  begin
    /* Если сущности есть */
    if ((RENTS is not null) and (RENTS.COUNT > 0)) then
      /* Начинаем формирование XML */
      PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
      /* Добавляем сущности */
      TENTS_TO_XML(RENTS => RENTS);
      /* Сериализуем */
      CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
    else
      CRES := null;
    end if;
    /* Возвращаем полученное */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_ENTS_TO_XML;
  
  /* Десериализация сущностей запроса */
  function QUERY_ENTS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции сущностей запроса
  ) return                  TENTS                -- Коллекция сущностей
  is
    RENTS                   TENTS;               -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Инициализируем результат */
    RENTS := TENTS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считываем узел сущностей */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENTS);
        /* Десериализуем его */
        RENTS := TENTS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём сформированное */
    return RENTS;
  end QUERY_ENTS_FROM_XML;

  /* Сериализация связей запроса */
  function QUERY_RLS_TO_XML
  (
    RRLS                    in TRLS     -- Коллекция связей
  ) return                  clob        -- XML-описание
  is
    CRES                    clob;       -- Буфер для результата
  begin
    /* Если связи есть */
    if ((RRLS is not null) and (RRLS.COUNT > 0)) then
      /* Начинаем формирование XML */
      PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
      /* Добавляем связи */
      TRLS_TO_XML(RRLS => RRLS);
      /* Сериализуем */
      CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
    else
      CRES := null;
    end if;
    /* Возвращаем полученное */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_RLS_TO_XML;
  
  /* Десериализация связей запроса */
  function QUERY_RLS_FROM_XML
  (
    CXML                    in clob              -- XML-описание коллекции связей запроса
  ) return                  TRLS                 -- Коллекция связей
  is
    RRLS                    TRLS;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Инициализируем результат */
    RRLS := TRLS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считываем узел связей */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RLS);
        /* Десериализуем его */
        RRLS := TRLS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём сформированное */
    return RRLS;
  end QUERY_RLS_FROM_XML;
  
  /* Сериализация настройки запроса */
  function QUERY_OPT_TO_XML
  (
    ROPT                    in TOPT     -- Настройка
  ) return                  clob        -- XML-описание
  is
    CRES                    clob;       -- Буфер для результата
  begin
    /* Начинаем формирование XML */
    PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
    /* Формируем XML настройки */
    TOPT_TO_XML(ROPT => ROPT);
    /* Сериализуем */
    CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
    /* Завершаем формирование XML */
    PKG_XFAST.EPILOGUE();
    /* Возвращаем полученное */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_OPT_TO_XML;
  
  /* Десериализация настройки запроса */
  function QUERY_OPT_FROM_XML
  (
    CXML                    in clob              -- XML-описание настройки запроса
  ) return                  TOPT                 -- Настройка
  is
    ROPT                    TOPT;                -- Буфер для результата
    XDOC                    PKG_XPATH.TDOCUMENT; -- Документ XML
    XROOT                   PKG_XPATH.TNODE;     -- Корень документа XML
    XNODE                   PKG_XPATH.TNODE;     -- Буфер узла документа
  begin
    /* Инициализируем результат */
    ROPT.RARGS := TARGS();
    /* Если данные есть */
    if (CXML is not null) then
      begin
        /* Разбираем XML */
        XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML);
        /* Считываем корневой узел */
        XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
        /* Считываем узел настройки */
        XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_OPT);
        /* Десериализуем его */
        ROPT := TOPT_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE));
        /* Освободим документ */
        PKG_XPATH.FREE(RDOCUMENT => XDOC);
      exception
        when others then
          /* Освободим документ */
          PKG_XPATH.FREE(RDOCUMENT => XDOC);
          /* Вернем ошибку */
          PKG_STATE.DIAGNOSTICS_STACKED();
          P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
      end;
    end if;
    /* Вернём сформированное */
    return ROPT;
  end QUERY_OPT_FROM_XML;
  
  /* Сериализация запроса */
  function QUERY_TO_XML
  (
    NRN                     in number               -- Рег. номер запроса
  ) return                  clob                    -- XML-описание
  is
    RQ                      P8PNL_QE_QUERY%rowtype; -- Запись запроса
    CRES                    clob;                   -- Буфер для сериализации
  begin
    /* Читаем запрос */
    RQ := QUERY_GET(NRN => NRN);
    /* Начинаем формирование XML */
    PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
    /* Открываем корень */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
    /* Настройка */
    if (RQ.OPT is not null) then
      TOPT_TO_XML(ROPT => QUERY_OPT_FROM_XML(CXML => RQ.OPT));
    end if;
    /* Сущности (можно использовать просто PKG_XFAST.VALUE_XML и сразу отдать готовый XML из RQ.ENTS, но это приводит к формированию некорректного документа с лишней ">" между группами) */
    if (RQ.ENTS is not null) then
      TENTS_TO_XML(RENTS => QUERY_ENTS_FROM_XML(CXML => RQ.ENTS));
    end if;
    /* Связи (можно использовать просто PKG_XFAST.VALUE_XML и сразу отдать готовый XML из RQ.RLS, но это приводит к формированию некорректного документа с лишней ">" между группами) */
    if (RQ.RLS is not null) then
      TRLS_TO_XML(RRLS => QUERY_RLS_FROM_XML(CXML => RQ.RLS));
    end if;
    /* SQL-выражение */
    PKG_XFAST.HERB(SNAME => STAG_QRY, LCVALUE => RQ.QRY);
    /* SQL-выражение с подстановками */
    PKG_XFAST.HERB(SNAME => STAG_QRY_BND, LCVALUE => RQ.QRY_BND);
    /* Сообщение при формировании SQL-выражения */
    PKG_XFAST.HERB(SNAME => STAG_QRY_MSG, LCVALUE => RQ.QRY_MSG);
    /* Закрываем корень */
    PKG_XFAST.UP();
    /* Сериализуем */
    CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
    /* Завершаем формирование XML */
    PKG_XFAST.EPILOGUE();
    /* Возвращаем результат */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_TO_XML;
  
  /* Формирование описания запроса */
  function QUERY
  (
    NRN                     in number   -- Рег. номер запроса
  ) return                  clob        -- XML-описание
  is
  begin
    /* Сериализуем запрос */
    return QUERY_TO_XML(NRN => NRN);
  end QUERY;
  
  /* Формирование списка запросов */
  function QUERY_LIST
  (
    SUSER                   in varchar2 -- Имя пользователя
  ) return                  clob        -- Список запросов
  is
    CRES                    clob;       -- Буфер для сериализации
  begin
    /* Начинаем формирование XML */
    PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
    /* Открываем корень */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
    /* Открываем список запросов */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_QUERIES);
    /* Обходим запросы - данного пользователя и публичные */
    for C in (select T.RN NRN,
                     T.CODE SCODE,
                     T.NAME SNAME,
                     UL.NAME SAUTHOR,
                     TO_CHAR(T.CH_DATE, 'dd.mm.yyyy hh24:mi:ss') SCH_DATE,
                     T.READY NREADY,
                     T.PBL NPBL,
                     QUERY_ACCESS_SIGN_MODIFY(T.RN, SUSER) NMODIFY
                from P8PNL_QE_QUERY T,
                     USERLIST       UL
               where T.AUTHOR = UL.AUTHID
                 and QUERY_ACCESS_SIGN_VIEW(T.RN, SUSER) = 1
               order by T.CODE)
    loop
      /* Открываем описание запроса */
      PKG_XFAST.DOWN_NODE(SNAME => STAG_QUERY);
      /* Запрос */
      PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => C.NRN);
      PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => C.SCODE);
      PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => C.SNAME);
      PKG_XFAST.ATTR(SNAME => SATTR_AUTHOR, SVALUE => C.SAUTHOR);
      PKG_XFAST.ATTR(SNAME => SATTR_CH_DATE, SVALUE => C.SCH_DATE);
      PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => C.NREADY);
      PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => C.NPBL);
      PKG_XFAST.ATTR(SNAME => SATTR_MODIFY, NVALUE => C.NMODIFY);
      /* Закрываем описание запроса */
      PKG_XFAST.UP();
    end loop;
    /* Закрываем список запросов */
    PKG_XFAST.UP();
    /* Закрываем корень */
    PKG_XFAST.UP();
    /* Сериализуем */
    CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
    /* Завершаем формирование XML */
    PKG_XFAST.EPILOGUE();
    /* Возвращаем результат */
    return CRES;
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_LIST;
  
  /* Добавление запроса */
  procedure QUERY_INSERT
  (
    SCODE                   in varchar2, -- Мнемокод
    SNAME                   in varchar2, -- Наименование
    NRN                     out number   -- Рег. номер добавленного запроса
  )
  is
  begin
    /* Проверим параметры */
    QUERY_CHECK(SCODE => SCODE, SNAME => SNAME);
    /* Формируем рег. номер */
    NRN := GEN_ID();
    /* Добавляем данные */
    insert into P8PNL_QE_QUERY
      (RN, CODE, name, AUTHOR, CH_DATE, READY, PBL)
    values
      (NRN, SCODE, SNAME, UTILIZER(), sysdate, 0, 0);
  end QUERY_INSERT;
  
  /* Исправление запроса */
  procedure QUERY_UPDATE
  (
    NRN                     in number,   -- Рег. номер запроса
    SCODE                   in varchar2, -- Мнемокод
    SNAME                   in varchar2  -- Наименование
  )
  is
  begin
    /* Проверим параметры */
    QUERY_CHECK(SCODE => SCODE, SNAME => SNAME);
    /* Изменяем данные */
    update P8PNL_QE_QUERY T
       set T.CODE = SCODE,
           T.NAME = SNAME
     where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_UPDATE;
  
  /* Удаление запроса */
  procedure QUERY_DELETE
  (
    NRN                     in number   -- Рег. номер запроса
  )
  is
  begin
    /* Удаляем запись */
    delete from P8PNL_QE_QUERY T where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
  end QUERY_DELETE;
  
  /* Чтение сущностей запроса */
  function QUERY_ENTS_GET
  (
    CENTS                   in clob     -- Сериализованное описание сущностей
  ) return                  TENTS       -- Коллекция сущностей
  is
  begin
     /* Десериализуем */
     return QUERY_ENTS_FROM_XML(CXML => CENTS);
  end QUERY_ENTS_GET;
  
  /* Запись сущностей запроса */
  procedure QUERY_ENTS_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    RENTS                   in TENTS    -- Коллекция сущностей
  )
  is
    CENTS                   clob;       -- Буфер для сериализации
  begin
    /* Сериализуем полученную коллекцию сущностей */
    CENTS := QUERY_ENTS_TO_XML(RENTS => RENTS);
    /* Сохраним её */
    update P8PNL_QE_QUERY T set T.ENTS = CENTS where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_ENTS_SET;
  
  /* Получение состава атрибутов сущности */
  procedure QUERY_ENT_ATTRS_GET
  (
    NRN                     in number,              -- Рег. номер запроса
    SID                     in varchar2,            -- Идентификатор сущности
    COUT                    out clob                -- Сериализованное описание атрибутов сущности
  )
  is
    RQ                      P8PNL_QE_QUERY%rowtype; -- Запись запроса
    NENT_INDEX              PKG_STD.TNUMBER;        -- Индекс изменяемой сущности в коллекции
    RENTS                   TENTS;                  -- Коллекция существующих сущностей
    RENT                    TENT;                   -- Изменяемая сущность
    RATTRS                  TATTRS;                 -- Коллекция атрибутов изменяемой сущности
  begin
    /* Читаем запись запроса */
    RQ := QUERY_GET(NRN => NRN);
    /* Читаем существующие сущности */
    RENTS := QUERY_ENTS_GET(CENTS => RQ.ENTS);
    /* Читаем изменяемую сущность */
    NENT_INDEX := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
    if (NENT_INDEX is not null) then
      RENT := RENTS(NENT_INDEX);
    else
      P_EXCEPTION(0,
                  'Сущность с идентификатором "%s" не определена.',
                  COALESCE(SID, '<НЕ УКАЗАН>'));
    end if;
    /* Получим полный набор атрибутов сущности */
    RATTRS := TATTRS_MAKE(RENT => RENT);
    /* Начинаем формирование XML */
    PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
    /* Открываем корень */
    PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
    /* Формируем описание тарибутов */
    TATTRS_TO_XML(RATTRS => RATTRS);
    /* Закрываем корень */
    PKG_XFAST.UP();
    /* Сериализуем */
    COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
    /* Завершаем формирование XML */
    PKG_XFAST.EPILOGUE();
  exception
    when others then
      /* Завершаем формирование XML */
      PKG_XFAST.EPILOGUE();
      /* Вернем ошибку */
      PKG_STATE.DIAGNOSTICS_STACKED();
      P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
  end QUERY_ENT_ATTRS_GET;

  /* Чтение связей запроса */
  function QUERY_RLS_GET
  (
    CRLS                    in clob     -- Сериализованное описание связей
  ) return                  TRLS        -- Коллекция связей
  is
  begin
    /* Десериализуем */
    return QUERY_RLS_FROM_XML(CXML => CRLS);
  end QUERY_RLS_GET;
  
  /* Запись связей запроса */
  procedure QUERY_RLS_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    RRLS                    in TRLS     -- Коллекция связей
  )
  is
    CRLS                    clob;       -- Буфер для сериализации
  begin
    /* Сериализуем полученную коллекцию связей */
    CRLS := QUERY_RLS_TO_XML(RRLS => RRLS);
    /* Сохраним её */
    update P8PNL_QE_QUERY T set T.RLS = CRLS where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_RLS_SET;
  
  /* Установка признака "готовности" запроса */
  procedure QUERY_READY_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    NREADY                  in number   -- Флаг готовности к использованию (0 - нет, 1 - да)
  )
  is
  begin
    /* Проверим параметры */
    if (NREADY is null) then
      P_EXCEPTION(0,
                  'Не задано значение признака готовности запроса к использованию.');
    end if;
    if (NREADY not in (0, 1)) then
      P_EXCEPTION(0,
                  'Значение признака готовности запроса к использованию задано некорректно (ожидалось 0 или 1).');
    end if;
    /* Установим флаг готовности к использованию */
    update P8PNL_QE_QUERY T set T.READY = NREADY where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_READY_SET;
  
  /* Установка признака "публичности" запроса */
  procedure QUERY_PBL_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    NPBL                    in number   -- Флаг публичности (0 - приватный, 1 - публичный)
  )
  is
  begin
    /* Проверим параметры */
    if (NPBL is null) then
      P_EXCEPTION(0, 'Не задано значение признака публичности запроса.');
    end if;
    if (NPBL not in (0, 1)) then
      P_EXCEPTION(0,
                  'Значение признака публичноти запроса задано некорректно (ожидалось 0 или 1).');
    end if;
    /* Установим флаг публичности */
    update P8PNL_QE_QUERY T set T.PBL = NPBL where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_PBL_SET;

  /* Чтение настройки запроса */
  function QUERY_OPT_GET
  (
    COPT                    in clob     -- Сериализованное описание настройки запроса
  ) return                  TOPT        -- Настройка запроса
  is
  begin
    /* Десериализуем */
    return QUERY_OPT_FROM_XML(CXML => COPT);
  end QUERY_OPT_GET;
  
  /* Запись настройки запроса */
  procedure QUERY_OPT_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    ROPT                    in TOPT     -- Настройка запроса
  )
  is
    COPT                    clob;       -- Буфер для сериализации
  begin
    /* Сериализуем настройку */
    COPT := QUERY_OPT_TO_XML(ROPT => ROPT);
    /* Сохраним её */
    update P8PNL_QE_QUERY T set T.OPT = COPT where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_OPT_SET;
  
  /* Запись SQL-выражения запроса */
  procedure QUERY_QRY_SET
  (
    NRN                     in number,  -- Рег. номер запроса
    CQRY                    in clob,    -- SQL-выражение
    CQRY_BND                in clob,    -- SQL-выражение с подставленными значениями аргументов
    CQRY_MSG                in clob     -- Сообщение при формировании SQL-выражения (предупреждения и ошибки формирования)
  )
  is
  begin
    /* Сохраним её */
    update P8PNL_QE_QUERY T
       set T.QRY     = CQRY,
           T.QRY_BND = CQRY_BND,
           T.QRY_MSG = CQRY_MSG
     where T.RN = NRN;
    /* Контроль изменения данных */
    if (sql%notfound) then
      PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY');
    end if;
    /* Обновим дату изменения запроса */
    QUERY_CH_DATE_SYNC(NRN => NRN);
  end QUERY_QRY_SET;
  
  /* Обновление SQL-выражения запроса */
  procedure QUERY_QRY_REFRESH
  (
    NRN                     in number         -- Рег. номер запроса
  )
  is
    SQRY                    PKG_STD.TLSTRING; -- SQL-выражение запроса
    SQRY_BND                PKG_STD.TLSTRING; -- SQL-выражение запроса с подстановками
    SQRY_MSG                PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
  begin
    /* Сформируем SQL-выражение */
    QUERY_QRY_BUILD(NRN => NRN, SQRY => SQRY, SQRY_BND => SQRY_BND, SQRY_MSG => SQRY_MSG);
    /* Обновим его в запросе */
    QUERY_QRY_SET(NRN => NRN, CQRY => SQRY, CQRY_BND => SQRY_BND, CQRY_MSG => SQRY_MSG);
  end QUERY_QRY_REFRESH;
  
  /* Формирование SQL-выражения запроса */
  procedure QUERY_QRY_BUILD
  (
    NRN                     in number,              -- Рег. номер запроса
    SQRY                    out varchar2,           -- SQL-выражение
    SQRY_BND                out varchar2,           -- SQL-выражение с подставленными значениями аргументов
    SQRY_MSG                out varchar2            -- Сообщение при формировании SQL-выражения (предупреждения и ошибки формирования)
  )
  is
    RQ                      P8PNL_QE_QUERY%rowtype; -- Запись запроса
    RENTS                   TENTS;                  -- Сущности запроса
    RRLS                    TRLS;                   -- Связи запроса
    ROPT                    TOPT;                   -- Настройка запроса
    
    /* Кумулятивная установка сообщения формирования запроса */
    procedure ADD_QRY_MSG
    (
      SMESSAGE              in varchar2,       -- Текст сообщения
      BCLEAR_QRY            in boolean := true -- Флаг очистки сформированного запроса
    )
    is
    begin
      if (BCLEAR_QRY) then
        SQRY     := null;
        SQRY_BND := null;
      end if;
      if (SQRY_MSG is null) then
        SQRY_MSG := SMESSAGE;
      else
        SQRY_MSG := SQRY_MSG || CHR(10) || CHR(13) || SMESSAGE;
      end if;
    end ADD_QRY_MSG;
    
    /* Проверка соответствия условий запроса набору его аргументов */
    procedure CHECK_COND_ARGS
    (
      ROPT                  TOPT                       -- Настройка запроса
    )
    is
      RCOND_VARS            PKG_CONTVARLOC.TCONTAINER; -- Коллекция переменных условия запроса
      SCOND_VAR             PKG_STD.TSTRING;           -- Текущая переменная условия запроса
    begin
      /* Извлечен переменные из условия запроса */
      RCOND_VARS := PKG_SQL_BUILD.VAR_LIST(SSQL => ROPT.SCOND);
      /* Если переменные в условии есть */
      if (PKG_CONTVARLOC.COUNT_(RCONTAINER => RCOND_VARS) > 0) then
        /* Обходим их */
        SCOND_VAR := PKG_CONTVARLOC.FIRST_(RCONTAINER => RCOND_VARS);
        while (SCOND_VAR is not null)
        loop
          /* Проверяем вхождение текущей переменной в состав аргументов запроса */
          if (TARGS_INDEX_BY_NAME(RARGS => ROPT.RARGS, SNAME => SCOND_VAR) is null) then
            /* В аргументах такой нет - добавляем предупреждение */
            ADD_QRY_MSG(SMESSAGE   => 'Переменная условия запроса "' || SCOND_VAR ||
                                      '" отсутствует в составе его аргументов.',
                        BCLEAR_QRY => false);
          end if;
          /* Ищем следующу */
          SCOND_VAR := PKG_CONTVARLOC.NEXT_(RCONTAINER => RCOND_VARS, SNAME => SCOND_VAR);
        end loop;
      end if;
    end CHECK_COND_ARGS;
    
    /* Поиск корневой (не имеет связей по выходу - т.е. нигде не является источником) сущности запроса */
    procedure FIND_ROOT_ENT
    (
      RENTS                 TENTS,       -- Сущности запроса
      RRLS                  TRLS,        -- Связи запроса
      RENT                  out TENT,    -- Найденная корневая сущность
      SMESSAGE              out varchar2 -- Сообщение от алгоритма поиска
    ) 
    is
      BFOUND                boolean;     -- Флаг наличия атрибута сущности в составе источникв связей запроса
    begin
      /* Если сущность одна - то она и главная */
      if (RENTS.COUNT = 1) then
        RENT := RENTS(RENTS.FIRST);
        return;
      end if;
      /* Обходим все сущности */
      for E in RENTS.FIRST .. RENTS.LAST
      loop
        /* Сброс флага наличия атрибутов в источниках связей */
        BFOUND := false;
        /* Обходим атрибуты сущности */
        for A in RENTS(E).RATTRS.FIRST .. RENTS(E).RATTRS.LAST
        loop
          /* Обойдем связи и поищем атрибут среди источников */
          if ((RRLS is not null) and (RRLS.COUNT > 0)) then
            for R in RRLS.FIRST .. RRLS.LAST
            loop
              if (RRLS(R).SSOURCE = RENTS(E).RATTRS(A).SID) then
                BFOUND := true;
                exit;
              end if;
            end loop;
          end if;
          /* Если хоть один атрибут сущности есть среди источников - дальше можно атрибуты этой сущности не рассматривать */
          exit when BFOUND = true;
        end loop;
        /* Если ни один из атрибутов не был источником - сущность наш клиент */
        if (not BFOUND) then
          /* Если мы ещё не находили корневую сущность ранее */
          if (RENT.SID is null) then
            RENT := RENTS(E);
          else
            /* Ранее была найдена корневая сущность */
            SMESSAGE := 'В запросе более одной корневой (без выходных связей) сущности.';
            return;
          end if;
        end if;
      end loop;
      /* Проверим, что хоть что-то нашли */
      if (RENT.SID is null) then
        SMESSAGE := 'В запросе нет корневой (без выходных связей) сущности.';
      end if;
    end FIND_ROOT_ENT;
    
    /* Формирование части "select" */
    procedure BUILD_SELECT
    (
      RENTS                 TENTS,            -- Сущности запроса
      SQRY                  in out varchar2   -- Буфер для запроса
    )
    is
      SFIELD                PKG_STD.TSTRING;  -- Буфер для поля запроса
      BFOUND                boolean := false; -- Флаг наличия визуализируемых атрибутов
    begin
      /* Формируем */
      PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => 'select ');
      for I in RENTS.FIRST .. RENTS.LAST
      loop
        if ((RENTS(I).RATTRS is not null) and (RENTS(I).RATTRS.COUNT > 0)) then
          for J in RENTS(I).RATTRS.FIRST .. RENTS(I).RATTRS.LAST
          loop
            if (RENTS(I).RATTRS(J).NSHOW = 1) then
              if (RENTS(I).RATTRS(J).SALIAS is null) then
                P_EXCEPTION(0,
                            'Для атрибута ("%s") не задан севдоним.',
                            RENTS(I).RATTRS(J).SID);
              end if;
              SFIELD := RENTS(I).RATTRS(J).SID || ' "' || RENTS(I).RATTRS(J).SALIAS || '", ';
              PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => SFIELD);
              BFOUND := true;
            end if;
          end loop;
        end if;
      end loop;
      SQRY := RTRIM(SQRY, ', ');
      /* Если не нашли ни одного атрибута */
      if (not BFOUND) then
        P_EXCEPTION(0,
                    'Для запроса не определено ни одного визуализируемого атрибута.');
      end if;
    end BUILD_SELECT;
    
    /* Формирование иерархии связей в секции "from" */
    procedure BUILD_FROM_HIER
    (
      RENT_START            TENT,            -- Начало ветки
      RENTS                 TENTS,           -- Сущности запроса
      RRLS                  TRLS,            -- Связи запроса
      SQRY                  in out varchar2  -- Буфер для запроса      
    )
    is
      NENT_SRC              PKG_STD.TNUMBER; -- Индекс сущности-источника связи
      SJOIN                 PKG_STD.TSTRING; -- Буфер для операции объединения
      STABLE                PKG_STD.TSTRING; -- Буфер для таблицы запроса
    begin
      /* Обходим связи */
      for I in RRLS.FIRST .. RRLS.LAST
      loop
        /* Если это связь, в которой приёмником является стартовая сущность */
        if (TATTRS_INDEX_BY_ID(RATTRS => RENT_START.RATTRS, SID => RRLS(I).STARGET) is not null) then
          /* Найдем сущность-источник связи по атрибуту-источнику связи */
          NENT_SRC := TENTS_INDEX_BY_ATTR_ID(RENTS => RENTS, SATTR_ID => RRLS(I).SSOURCE);
          if (NENT_SRC is null) then
            P_EXCEPTION(0,
                        'Для связи "%s - %s" не удалось определить сущность-источник.',
                        RRLS                                                                                                    (I)
                        .SSOURCE,
                        RRLS                                                                                                    (I)
                        .STARGET);
          end if;
          /* Определим тип объединения */
          if (RRLS(I).NMANDATORY = 1) then
            SJOIN := 'join';
          else
            SJOIN := 'left join';
          end if;
          /* Соберем выражение и добавим его в запрос */
          STABLE := SJOIN || ' ' || RENTS(NENT_SRC).SNAME || ' ' || RENTS(NENT_SRC).SID || ' on ' || RRLS(I).STARGET ||
                    ' = ' || RRLS(I).SSOURCE;
          PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => STABLE);
          /* Добавим связи сущности-источника */
          BUILD_FROM_HIER(RENT_START => RENTS(NENT_SRC), RENTS => RENTS, RRLS => RRLS, SQRY => SQRY);
        end if;
      end loop;
    end BUILD_FROM_HIER;
    
    /* Формирование части "from" */
    procedure BUILD_FROM
    (
      RENTS                 TENTS,           -- Сущности запроса
      RRLS                  TRLS,            -- Связи запроса
      SQRY                  in out varchar2  -- Буфер для запроса      
    )
    is
      RENT_ROOT             TENT;            -- Корневая сущность запроса
      STABLE                PKG_STD.TSTRING; -- Буфер для таблицы запроса
      SMESSAGE              PKG_STD.TSTRING; -- Буфер для сообщений
    begin
      /* Определяем корневую сущность запроса */
      FIND_ROOT_ENT(RENTS => RENTS, RRLS => RRLS, RENT => RENT_ROOT, SMESSAGE => SMESSAGE);
      /* Продолжаем только если корневую успешно определили */
      if ((RENT_ROOT.SID is not null) and (SMESSAGE is null)) then
        PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => 'from ');
        STABLE := RENT_ROOT.SNAME || ' ' || RENT_ROOT.SID;
        PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => STABLE);
        if ((RRLS is not null) and (RRLS.COUNT > 0)) then
          BUILD_FROM_HIER(RENT_START => RENT_ROOT, RENTS => RENTS, RRLS => RRLS, SQRY => SQRY);
        end if;
      else
        /* Корневую сущность не нашли */
        ADD_QRY_MSG(SMESSAGE => SMESSAGE, BCLEAR_QRY => false);
      end if;
    end BUILD_FROM;
    
    /* Формирование части "where" */
    procedure BUILD_WHERE
    (
      ROPT                  TOPT,            -- Настройка запроса
      SQRY                  in out varchar2  -- Буфер для запроса
    )
    is      
    begin
      if (ROPT.SCOND is not null) then
        PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => 'where ' || ROPT.SCOND);
      end if;      
    end BUILD_WHERE;
    
    /* Подстановка отладочных значений в SQL-выражение */
    procedure BOUND
    (
      ROPT                  TOPT,        -- Настройка запроса
      SQRY                  in varchar2, -- Сформированное SQL-выражение
      SQRY_BND              out varchar2 -- SQL-выражение с подстановками
    )
    is
    begin
      SQRY_BND := SQRY;
      if ((ROPT.RARGS is not null) and (ROPT.RARGS.COUNT > 0)) then
        for I in ROPT.RARGS.FIRST .. ROPT.RARGS.LAST
        loop
          begin
            case
              when ROPT.RARGS(I).NDATA_TYPE = PKG_STD.DATA_TYPE_STR then
                SQRY_BND := PKG_SQL_BUILD.VAR_REPLACE_TO_STR(SSQL   => SQRY_BND,
                                                             SNAME  => ROPT.RARGS(I).SNAME,
                                                             SVALUE => ROPT.RARGS(I).SVALUE);
              when ROPT.RARGS(I).NDATA_TYPE = PKG_STD.DATA_TYPE_NUM then
                SQRY_BND := PKG_SQL_BUILD.VAR_REPLACE_TO_NUM(SSQL   => SQRY_BND,
                                                             SNAME  => ROPT.RARGS(I).SNAME,
                                                             NVALUE => TO_NUMBER(ROPT.RARGS(I).SVALUE));
              when ROPT.RARGS(I).NDATA_TYPE = PKG_STD.DATA_TYPE_DATE then
                SQRY_BND := PKG_SQL_BUILD.VAR_REPLACE_TO_DATE(SSQL   => SQRY_BND,
                                                              SNAME  => ROPT.RARGS(I).SNAME,
                                                              DVALUE => TO_DATE(ROPT.RARGS(I).SVALUE, 'yyyy-mm-dd'));
              else
                SQRY_BND := PKG_SQL_BUILD.VAR_REPLACE_TO_ANY(SSQL  => SQRY_BND,
                                                             SNAME => ROPT.RARGS(I).SNAME,
                                                             SANY  => ROPT.RARGS(I).SVALUE);
            end case;
          exception
            when others then
              ADD_QRY_MSG(SMESSAGE   => 'Ошибка конвертации при подстановке значения "' || ROPT.RARGS(I).SVALUE ||
                                        '" в аргумент запроса "' || ROPT.RARGS(I).SNAME || '".',
                          BCLEAR_QRY => false);
          end;
        end loop;
      end if;
    end BOUND;
  begin
    /* Читаем описание запроса */
    RQ := QUERY_GET(NRN => NRN);
    /* Читаем сущности запроса */
    RENTS := QUERY_ENTS_GET(CENTS => RQ.ENTS);
    /* Нет сущностей - нет запроса */
    if ((RENTS is null) or (RENTS.COUNT = 0)) then
      ADD_QRY_MSG(SMESSAGE => 'В запросе нет сущностей.');
      return;
    end if;
    /* Читаем связи запроса */
    RRLS := QUERY_RLS_GET(CRLS => RQ.RLS);
    /* Нельзя построить запрос, если есть несвязанные сущности */
    if ((RENTS.COUNT > 1) and ((RRLS is null) or (RENTS.COUNT - 1 > RRLS.COUNT))) then
      ADD_QRY_MSG(SMESSAGE => 'В запросе есть несвязанные сущности.');
      return;
    end if;
    /* Читаем настройку запроса */
    ROPT := QUERY_OPT_GET(COPT => RQ.OPT);
    /* Проверяем параметры запроса */
    CHECK_COND_ARGS(ROPT => ROPT);
    /* Добавляем подсказку совместимости */
    SQRY := PKG_SQL_BUILD.COMPATIBLE(SSQL => SQRY);
    /* Собираем запрос - поля */
    BUILD_SELECT(RENTS => RENTS, SQRY => SQRY);
    /* Собираем запрос - таблицы и связи */
    BUILD_FROM(RENTS => RENTS, RRLS => RRLS, SQRY => SQRY);
    /* Собираем запрос - условия */
    BUILD_WHERE(ROPT => ROPT, SQRY => SQRY);
    /* Сделаем подстановки */
    BOUND(ROPT => ROPT, SQRY => SQRY, SQRY_BND => SQRY_BND);
  exception
    when others then
      PKG_STATE.DIAGNOSTICS_STACKED();
      ADD_QRY_MSG(SMESSAGE => PKG_STATE.SQL_ERRM(), BCLEAR_QRY => false);
  end QUERY_QRY_BUILD;

end PKG_P8PANELS_QE_BASE;
/
