При настройке системы прав доступа (RLS — Record Level Security) часто возникает задача ограничить чтение элементов справочника Справочник.Номенклатура на основании разрешенных групп. При этом требуется, чтобы ограничение автоматически распространялось не только на прямых потомков указанной группы, но и на все подгруппы любой глубины вложенности.
Попытка решить эту задачу прямым запросом в тексте ограничения RLS через соединение с родителем приводит к тому, что условие срабатывает только на один уровень иерархии. Разберем, почему рекурсию нельзя и не следует строить прямо в тексте RLS, и рассмотрим проверенные архитектурные решения этой проблемы.
Проанализируем технические ограничения платформы 1С:Предприятие при работе с RLS:
В ИЕРАРХИИ: Использование оператора В ИЕРАРХИИ внутри условий RLS со сложными подзапросами приводит к существенному падению производительности. На уровне СУБД это порождает множественные подзапросы и сканирование таблиц (Table Scan), что вызывает критические блокировки при параллельной работе пользователей.Рассмотрим основные способы реализации доступа к иерархическим данным, исключающие тяжелые вычисления в момент выборки данных.
Если состав групп номенклатуры и права пользователей меняются относительно редко, наиболее производительным решением является предрасчет доступных папок при старте сеанса пользователя.
Разберем реализацию по шагам:
ДоступныеГруппыНоменклатуры с типом ФиксированныйМассив.Посмотрим на пример функции формирования массива разрешенных групп:
Функция ПолучитьМассивДоступныхГрупп(ТекущийПользователь)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Регистр.ГруппаНоменклатуры КАК Группа
|ИЗ
| РегистрСведений.ДоступныеГруппыНоменклатурыПользователя КАК Регистр
|ГДЕ
| Регистр.Пользователь = &ТекущийПользователь";
Запрос.УстановитьПараметр("ТекущийПользователь", ТекущийПользователь);
Выборка = Запрос.Выполнить().Выбрать();
МассивГрупп = Новый Массив;
Пока Выборка.Следующий() Цикл
// Получаем все вложенные группы с помощью запроса с оператором В ИЕРАРХИИ
ЗапросИерархии = Новый Запрос;
ЗапросИерархии.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ЭтоГруппа = ИСТИНА
| И Номенклатура.Ссылка В ИЕРАРХИИ(&РодительскаяГруппа)";
ЗапросИерархии.УстановитьПараметр("РодительскаяГруппа", Выборка.Группа);
ВыборкаИерархии = ЗапросИерархии.Выполнить().Выбрать();
Пока ВыборкаИерархии.Следующий() Цикл
МассивГрупп.Добавить(ВыборкаИерархии.Ссылка);
КонецЦикла;
КонецЦикла;
Возврат Новый ФиксированныйМассив(МассивГрупп);
КонецФункции
Текст ограничения RLS для справочника Номенклатура при этом становится предельно простым и быстрым:
ГДЕ Родитель В (&ДоступныеГруппыНоменклатуры)
ИЛИ Ссылка В (&ДоступныеГруппыНоменклатуры)
Преимущество: Запрос RLS отрабатывает моментально без тяжелых соединений.
Ограничение: При добавлении новых вложенных папок в течение рабочего дня пользователю может потребоваться перезапуск сеанса для обновления массива в параметре сеанса.
В типовых конфигурациях 1С широко используется подход с денормализацией прав доступа с помощью специального реквизита.
Разберем механизм работы:
Номенклатура добавляется реквизит ГруппаДоступа (ссылка на отдельный справочник ГруппыДоступаНоменклатуры).ПередЗаписью объекта или в подписке на событие настраивается автоматическое наследование группы доступа: если создается новый элемент или группа, реквизит ГруппаДоступа автоматически копируется из вышестоящего родителя.
Номенклатура
ГДЕ Номенклатура.ГруппаДоступа В (&ДоступныеГруппыДоступаПользователя)
Такой подход устраняет необходимость рекурсивного анализа структуры при чтении данных и гарантирует моментальное применение прав ко всем созданным объектам.
Если структура каталога меняется динамически, а перезапуск сеансов недопустим, классическим решением реляционных баз данных является использование вспомогательного регистра сведений (таблицы транзитивного замыкания).
Рассмотрим структуру регистра ИерархияНоменклатуры:
Родитель: Ссылка на вышестоящую группу любого уровня.Потомок: Ссылка на подчиненную номенклатуру или вложенную подгруппу.При записи любого элемента или группы номенклатуры в подписке на событие ПриЗаписи выполняется пересчет строк в регистре для измененного элемента и его потомков. В результате регистр содержит плоский список всех связей «Предок — Потомок».
Текст RLS в этом случае строится через быстрое соединение по индексированным измерениям:
Номенклатура
ГДЕ Номенклатура.Ссылка В
(ВЫБРАТЬ
Иерархия.Потомок
ИЗ
РегистрСведений.ИерархияНоменклатуры КАК Иерархия
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДоступныеГруппыНоменклатурыПользователя КАК ДоступныеГруппы
ПО Иерархия.Родитель = ДоступныеГруппы.ГруппаНоменклатуры
ГДЕ
ДоступныеГруппы.Пользователь = &ТекущийПользователь)
Подведем итоги выбора архитектуры: