При настройке ограничений доступа на уровне записей (RLS) в системе «1С:Предприятие» разработчики часто сталкиваются со странным поведением запросов к регистрам сведений. Рассмотрим типичную ситуацию: в базе данных создан независимый непериодический регистр сведений ПосещениеСалонов, у которого определено измерение Подразделение и ресурсы Пол и Вход.
На регистр наложено ограничение доступа по подразделениям. Однако при попытке выбрать только доступные подразделения простым запросом, в результат выборки попадают абсолютно все записи регистра, а недоступные пользователю элементы справочника отображаются как <Объект не найден>:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ПосещениеСалонов.Подразделение КАК Подразделение
ИЗ
РегистрСведений.ПосещениеСалонов КАК ПосещениеСалонов
ГДЕ
ПосещениеСалонов.ПериодДатаВремя МЕЖДУ &НачалоПериода И &КонецПериода
При этом, если добавить в этот же запрос хотя бы один ресурс (например, Вход), RLS начинает отрабатывать корректно, и все запрещенные записи успешно отсекаются еще на уровне СУБД. Выясним причину такого поведения платформы и подробно разберем по шагам несколько эффективных способов решения этой проблемы.
Проанализируем архитектурную особенность платформы «1С:Предприятие». В официальном руководстве разработчика зафиксировано следующее важное правило:
«Невозможно наложение ограничений на чтение измерений регистра сведений, если в регистре определено еще хотя бы одно поле, отличное от измерений (то есть ресурсы или реквизиты)».
Когда мы пишем первый запрос и выбираем исключительно измерения регистра, оптимизатор запросов платформы 1С видит, что обращения к ресурсам или реквизитам нет. В целях оптимизации производительности платформа полностью игнорирует и не накладывает ограничение RLS, заданное для самого регистра сведений. В итоге из базы считываются абсолютно все физические записи.
Но почему тогда в результате запроса вместо наименований подразделений выводится <Объект не найден>? Дело в том, что на сам справочник Подразделения (ссылочный тип данных измерения) у пользователя также наложено ограничение доступа. Платформа честно считывает ссылку из регистра, но при попытке получить представление объекта справочника срабатывает RLS справочника, и система скрывает данные, заменяя их на пустую ссылку или строку «Объект не найден».
Если же мы добавляем в выборку ресурс (поле Вход), платформа классифицирует запрос как обращение к «прочим полям» регистра и обязана применить RLS самого регистра сведений. Запрещенные записи отсекаются СУБД на этапе чтения таблицы регистра, и строки с недоступными подразделениями вообще не попадают в результат.
Рассмотрим подробнее четыре практических способа, как обойти это ограничение и получить из регистра сведений только разрешенные подразделения, не добавляя лишние ресурсы в итоговую выборку.
Самый простой способ заставить платформу принудительно применить ограничение RLS регистра сведений — выбрать ресурс во вложенном запросе, а во внешнем запросе исключить его из списка полей выборки. Разберем этот пример:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВложенныйЗапрос.Подразделение КАК Подразделение
ИЗ
(ВЫБРАТЬ РАЗРЕШЕННЫЕ
ПосещениеСалонов.Подразделение КАК Подразделение,
ПосещениеСалонов.Вход КАК Вход // Запрашиваем ресурс для срабатывания RLS
ИЗ
РегистрСведений.ПосещениеСалонов КАК ПосещениеСалонов
ГДЕ
ПосещениеСалонов.Period МЕЖДУ &НачалоПериода И &КонецПериода) КАК ВложенныйЗапрос
В данном случае во вложенном запросе мы принудительно выбираем поле Вход. Платформа применяет шаблон ограничения прав доступа к регистру, отсекая все недоступные пользователю записи. Внешний запрос получает уже очищенный массив данных и выводит только уникальные разрешенные подразделения.
Для более сложных и ресурсоемких запросов (например, при подготовке данных для отчетов) рекомендуется использовать механизм временных таблиц. Это делает код более структурированным и облегчает оптимизатору СУБД построение плана запроса. Посмотрим на пример реализации:
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
ПосещениеСалонов.Подразделение КАК Подразделение,
ПосещениеСалонов.Вход КАК Вход // Фиктивное поле для инициации RLS
ПОМЕСТИТЬ Вт_РазрешенныеЗаписи
ИЗ
РегистрСведений.ПосещениеСалонов КАК ПосещениеСалонов
ГДЕ
ПосещениеСалонов.ПериодДатаВремя МЕЖДУ &НачалоПериода И &КонецПериода
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Вт_РазрешенныеЗаписи.Подразделение КАК Подразделение
ИЗ
Вт_РазрешенныеЗаписи КАК Вт_РазрешенныеЗаписи
Мы разбиваем задачу на два шага. Сначала собираем данные во временную таблицу Вт_РазрешенныеЗаписи с использованием ключевого слова РАЗРЕШЕННЫЕ и любого числового или булевого ресурса. На втором шаге мы просто выбираем поле Подразделение из временной таблицы, где все недоступные записи уже гарантированно отсутствуют.
Если на сам справочник Подразделения в системе настроено корректное и актуальное ограничение RLS, мы можем принудительно отсечь недоступные строки с помощью операции ВНУТРЕННЕЕ СОЕДИНЕНИЕ с физической таблицей этого справочника. Проанализируем этот запрос:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ПосещениеСалонов.Подразделение КАК Подразделение
ИЗ
РегистрСведений.ПосещениеСалонов КАК ПосещениеСалонов
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Подразделения КАК Подразделения
ПО ПосещениеСалонов.Подразделение = Подразделения.Ссылка
ГДЕ
ПосещениеСалонов.ПериодДатаВремя МЕЖДУ &НачалоПериода И &КонецПериода
Поскольку в запросе присутствует ключевое слово РАЗРЕШЕННЫЕ, система применит права доступа ко всем участвующим таблицам. RLS справочника Подразделения вернет СУБД только те элементы, к которым у текущего пользователя есть доступ. За счет внутреннего соединения все строки регистра сведений, содержащие недоступные подразделения, будут полностью исключены из итогового результата запроса.
Рассмотрим еще один изящный способ фильтрации данных без использования дополнительных соединений и временных таблиц. При обращении к реквизитам ссылочного типа через точку (механизм разыменования полей), платформа 1С неявно соединяет таблицы и выполняет проверку прав доступа к связанному объекту.
Если у пользователя нет прав на чтение конкретного элемента справочника, результатом разыменования ссылки (например, обращения к свойству Ссылка) станет значение NULL. Мы можем использовать это свойство в секции ГДЕ нашего запроса:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ПосещениеСалонов.Подразделение КАК Подразделение
ИЗ
РегистрСведений.ПосещениеСалонов КАК ПосещениеСалонов
ГДЕ
ПосещениеСалонов.ПериодДатаВремя МЕЖДУ &НачалоПериода И &КонецПериода
И НЕ ПосещениеСалонов.Подразделение.Ссылка ЕСТЬ NULL
Условие И НЕ ПосещениеСалонов.Подразделение.Ссылка ЕСТЬ NULL заставит платформу выполнить неявную проверку прав на чтение для каждого подразделения. Те записи регистра, которые ссылаются на запрещенные для пользователя элементы справочника, вернут значение NULL и будут отсечены фильтром на уровне базы данных.
Используя предложенные методы, мы можем легко обходить платформенные ограничения RLS, сохраняя при этом высокую производительность системы и чистоту результирующей выборки.