1С и чувствительность к регистру [поход на грабли]








Всем известно, что исполняемый код для платформы 1С не чувствителен к регистру символов. Некоторый особенные люди считают себя одаренными и пользуются этой возможностью, чтобы писать в своем уникальном стиле либо все маленькими буквами, либо наоборот большими. Оставим эти глупости на совести таких разработчиков, ведь нам же главное не «красота» в режиме конфигуратора, а чтобы обрабатываемые нами данные оставались аутентичными. Что бы «А» (код 1040) и «а» (код 1072) или «T» (код 84) и «t» (код 116) всегда оставались сами собой и превращались друг в друга только под нашим чутким контролем с помощью ВРег() и НРег(). К сожалению, бывает не всегда так, что может приводить к неожиданным ошибкам.

Предыстория

Как-то при переносе данных в одну животноводческую базу, я столкнулся со странным поведением выполнения довольно простого кода. У меня было множество животных с уникальными шифрами, описывающими их породу. Те, кто помнят школьные уроки по генетики, знают, что доминантные гены принято изображать большими буквами, а рецессивные — маленькими. Для остальных небольшая иллюстрация для понимания:

 

 размер имеет значение

Казалось бы, что загрузку написал правильно, но результат в базе не соответствовал данным источника — данные по некоторым животным были перепутаны, а по части вообще ничего не загрузилось. Пристальное изучение несоответствий показало, что путаница наблюдались в тех случаях, когда шифры животных различались не составом, а только регистром.

 

Поиск по данным в базе

Давайте попробуем воссоздать пример с картинки выше и создадим справочник "Виды цветов" с тремя элементами: АА -> красный, Аа -> розовый и аа -> белый. Проблему можно увидеть сразу, если попытаться наш код внести в стандартное поле "Код":

 

 скрин

Заметим, что таким образом мы можем задать элементу справочника код А00001 и при автонумерации получим А00002, А00003 и так далее. Так же мы можем задать код а00001 и получить а00002, а00003… Но если мы при наличии А00001 по какой-то причине захотим установить номер а00001, то получить "облом".

Аналогичное поведение при тестировании кодов/номеров я обнаружел у документов, задач, бизнес-процессов, планов видов характеристик, планов счетов и планов обмена. Но у планов видов расчетов, однако, разрешается создать одновременно элементы с номерами "А00001" и "а00001", что очень странно — тут можно было бы сослаться на то, что у плана видов расчетов в настройках отсутствует свойство включения/отключения контроля уникальности номера, но этого свойства так же нет и у плана обменов. В документации о такой выборочно действующей особенности поведения ничего не написано. Если я просто не увидел, то напишите в комментариях.

Кстати, поскольку удалось создать несколько видов расчета с идентичными кодами, то это прекрасный повод проверить результат функции НайтиПоКоду(). Только предварительно я воспользуюсь отсутствием у плана видов расчетов контроля уникальности номеров и добавлю еще один элемент с большой "А" — интересно какой из этих двух элементов будет выбран:

 

 скрин

Да, уж. Согласитесь, результат оказался неожиданным и он подтверждает ранее замеченное наблюдение, что для платформы 1С регистр символов как минимум в кодах/номерах значения не умеет. Выходит, создавая код на нашей платформе, программист получает по факту выполнения: 1040 = КодСимвола("А") = КодСимвола("а") = 1072 , и лишь конечный пользователь системы видит на экране реальные символы.

Но продолжим нашу проверку. Обычно на практике для всяких внешних кодов используют реквизиты — в моем случае было так же. Создадим такой для справочника "Виды цветов" и попробуем воспользоваться другим стандартным поисковым методом — НайтиПоРеквизиту():

 

 скрин

Как раз с этим я и столкнулся при переносе — в поиске по реквизиту регистр символов игнорируется.

Хотя, как оказывается, не обязательно быть программистом, что бы испытать дискомфорт при точном поиске — аналогичное поведение наблюдается и при использовании отборов СКД в динамическом списке (видимо одна и та же поисковая функция из внутренней библиотеки):

 

 скрин

Для полноты картины, давайте протестируем последний оставшийся метод — ПоискПоНаименованию() и получим тот же результат (даже требование "точного соответствия" не помогло):

 

 скрин

Итак, поисковые методы менеджеров объектов отказываются правильно искать чувствительные к регистру символов данные. А можно ли самостоятельно создать подобные методы с помощью механизма запросов? Давайте попробуем:

 

 скрины

Как видите, ни использования оператора "=" в тексте запроса, ни оператор "Подобно" не помогли — каждый раз выбираются все похожие элементы, игнорируя регистры символов.

Т.е. для текста запроса, который транслируется в SQL и выполняется во внешних СУБД, снова верно выражение: 1040 = КодСимвола("А") = КодСимвола("а") = 1072. Я уже приготовился, что все во что я верил ложно и в мире 1С будет справделиво ("А" = "а") = Истина, но к счастью хотя бы примитивное сравнение строк работает и нужную нам функцию все же можно создать:

 

 скрин

 

Поиск по коллекциям

С данными базы как мы уже убедились — грустно. А как дело обстоит с коллекциями? 

Массив — обнаружена чувствительность к регистру символов в обычном и фиксированном вариантах для метода Найти().

 

 код и результаты

Таблица значений — методы Найти() и НайтиСтроки() чувствительны к регистру.

 

 код и результаты

Список значений — метод НайтиПоЗначению() чувствителен к регистру символов.

 

 код и результаты

Структура — поисковые методы отсутствуют, а ключи регистр игнорируют.

 

код и результаты 

Соответствие — поисковые методы отсутствуют, но зато поддерживаются ключи в разных регистрах.

 

код и результаты 

 

Заключение

Я понимаю, что разработчики платформы хотели упростить работу бабушек из бухгалтерии, что бы те не тянулись к Shift (или даже CapsLock) и при наборе "вова" в поле ввода у них сразу выбрался "Вова". Только я отказываюсь понимать — почему из-за этого "облегчения труда" должны страдать разработчики конфигураций. Зачем и нам навязали отсутствие у строк регистра? Ведь именно нам-то как раз "вова" и "Вова" различать нужно (или пути в линуксах, или буквы "м" и "М" в форматных строках, или…). Да и самим пользователям иногда в списке нужно найти единственного человека с фамилией "Кар", а не увидеть сотню макарычей.

Выход, как мы видим, существует. Тут можно написать поисковую функцию с перепроверкой результата. Еще можно вместо поиска перед основным алгоритмом создать соответствие, где по строковым ключам загнать значение соответствующих ссылок. Но хотелось бы применять подобные костыли реже.

P.S. Если кто-нибудь до сих пор этого не знал, то открою тайну — пароль на вход в 1С тоже нечувствителен к регистру! 😉

18 Comments

  1. PowerBoy
    Зачем и нам навязали отсутствие у строк регистра?

    Разработчики 1С не виноваты, скорей разработчики MS SQL Serverа!

    Reply
  2. fxmike

    Господи, пароль не чувствителен к регистру! Сколько лет я был в неведении! Как теперь с этим жить? Почему? Зачем? о_О

    Reply
  3. DenisCh

    (1) Как не виновны, если они сами коллейшн регистронезависимый для базы ставят?

    А скуль умеет и так, и так…

    Reply
  4. A_Max

    1. Всё это поведение описано в документации и вполне логично понимается где будет регистрозависимый «поиск» в зависимости от используемого объекта.

    2. Про пароли ещё интересней. Хранится два хэша: от правильного пароля и от приведённого в верхний (или нижний уже точно не вспомню) регистр. Но для чего это сделано всё равно загадка) Наверно чтобы подбирать было проще. А ещё из-за кривой реализации сохранения рядом с хэшами можно было наблюдать пароль вообще в открытом виде!!!!

    Reply
  5. Dementor

    (1) такое же поведение в файловой базе.

    Reply
  6. zqzq
    P.S. Если кто-нибудь до сих пор этого не знал, то открою тайну — пароль на вход в 1С тоже нечувствителен к регистру! 😉

    Кстати, не совсем так, из справки:


    Конфигуратор 1С:Предприятие 8. Параметры информационной базы



    Проверка сложности паролей пользователей. Если данный параметр установлен, пароли пользователей должны удовлетворять следующим требованиям:

    длина пароля не должна быть менее значения, указанного в параметре Минимальная длина паролей пользователей;

    пароль должен состоять из символов, относящихся как минимум к трем из перечисленных групп:

    заглавные буквы;

    строчные буквы;

    цифры;

    специальные символы ;

    пароль не должен совпадать с именем пользователя;

    пароль не должен являться последовательностью символов.

    Если параметр не установлен, то проверка пароля в процессе аутентификации регистронезависима.

    Использование ограничений на пароли пользователей информационной базы не влияет на существующие пароли. Ограничения будут применены только при изменении существующего пароля или при добавлении нового пользователя информационной базы.

    Показать

    Reply
  7. qwinter
    Выход, как мы видим, существует. Тут можно написать поисковую функцию с перепроверкой результата. Еще можно вместо поиска перед основным алгоритмом создать соответствие, где по строковым ключам загнать значение соответствующих ссылок. Но хотелось бы применять подобные костыли реже.

    Зачем такие сложности? Если очень надо храните двоичные данные строки в строковом реквизите. Это элементарный код. Функции ПолучитьДвоичныеДанныеИзСтроки, ПолучитьДвоичныеДанныеИзHexСтроки и ПолучитьСтрокуИзДвоичныхДанных спасут ваш мир)))

    Reply
  8. Dementor

    (6) вот только про эту настройку знают единицы. За более чем десятилетнюю практику видел использование такой настройки только один раз. На крупных компаниях применяют доменную авторизацию, а в мелких предпочитают использовать стандартные пароли: «1», «123»… или вообще входят без паролей (если в базе только директор и главбух).

    Reply
  9. Dementor

    (7) тоже была такая идея, но не стал использоваться — хотел оставить для пользователя нормальную видимость.

    Reply
  10. qwinter

    (9) может я конечно чего то не учитываю, но не совсем понимаю проблемы сделать для пользователя нормальную видимость.

    Reply
  11. qwinter

    (9) На форме

    &НаСервере
    Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    
    НаименованиеСРегистром = ПолучитьСтрокуИзДвоичныхДанных(ПолучитьДвоичныеДанныеИзHexСтроки(ТекущийОбъект.ПолеДД));
    
    КонецПроцедуры
    
    &НаСервере
    Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    ТекущийОбъект.ПолеДД = ПолучитьДвоичныеДанныеИзСтроки(НаименованиеСРегистром);
    
    КонецПроцедуры
    

    Показать

    И если требуется в модуле менеджера, что бы выводить поле в представление или что бы подбор с регистром работал

    Процедура ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    Представление = ПолучитьСтрокуИзДвоичныхДанных(ПолучитьДвоичныеДанныеИзHexСтроки(Данные.ПолеДД));
    
    КонецПроцедуры
    
    Процедура ОбработкаПолученияДанныхВыбора(ДанныеВыбора, Параметры, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    Запрос = Новый Запрос;
    Запрос.Текст =
    «ВЫБРАТЬ ПЕРВЫЕ 10
    | Города.Ссылка КАК Ссылка
    |ИЗ
    | Справочник.Города КАК Города
    |ГДЕ
    | Города.ПолеДД ПОДОБНО &ПолеДД»;
    
    Запрос.УстановитьПараметр(«ПолеДД», Строка(ПолучитьДвоичныеДанныеИзСтроки(Параметры.СтрокаПоиска)) + «%»);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    Выборка = РезультатЗапроса.Выбрать();
    
    ДанныеВыбора = Новый СписокЗначений;
    Пока Выборка.Следующий() Цикл
    ДанныеВыбора.Добавить(Выборка.Ссылка);
    КонецЦикла;
    
    КонецПроцедуры
    
    Процедура ОбработкаПолученияПолейПредставления(Поля, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    Поля.Добавить(«ПолеДД»);
    
    КонецПроцедуры
    
    

    Показать

    Reply
  12. qwinter

    (9) Единственный минус, если поле слишком длинное, то индексировать не получиться. Максимальная срока 630 символов при котором поле 1с индексируется, а это получается 157 символов для поля с регистром. Так что реквизит формы желательно будет ограничить, если будет требоваться получать список выбора по полю.

    Reply
  13. Дмитрий74Чел

    (2) На сколько помню в конфигураторе есть флаг, который включает контроль.

    Reply
  14. Dementor

    (11) для поля наименования покатит (если представление по наименованию) и в динамических списках выводить не Наименование, а Ссылка. А что с реквизитами? На формах списков/выборов писать варианты ПриПолученииДанных (для ОФ и УФ по разному) и делать функции общих модулей для расчета представления полей в отчетах на компоновке? Как-то уж слишком большая цена за возможность регистрозависимого поиска — дешевле в модуле менеджера справочника дописать свою поисковую функцию.

    Reply
  15. qwinter

    (14)

    А что с реквизитами? На формах списков/выборов писать варианты ПриПолученииДанных (для ОФ и УФ по разному

    Зачем вообще такой реквизит нужен, если по нему не делается ввод по строке? Или не делается поиск в формах списка/выбора? Искать по наименованию в модулях? Я промолчу, что я об этом думаю.

    делать функции общих модулей для расчета представления полей в отчетах на компоновке?

    Зачем? Эти функции можно использовать в вычисляемых полях.

    дешевле в модуле менеджера справочника дописать свою поисковую функцию.

    Считывание лишних данных всегда дороже. К тому же отсутствие возможности создания поиска в формах списка/выбора.

    Reply
  16. SeiOkami

    (4)

    Хранится два хэша: от правильного пароля и от приведённого в верхний (или нижний уже точно не вспомню) регистр. Но для чего это сделано всё равно загадка) Наверно чтобы подбирать было проще.

    Это чтобы, в зависимости от настройки в базе, сравнивать либо с чувствительностью к регистру, либо без.

    Если без чувствительности, то введённый пользователем пароль вводится в верхний регистр и его хэш сравнивается с хэшом в базе.

    Reply
  17. SeiOkami

    А как сделать так, чтобы отборы СКД и динамических списков у пользователей «чувствительно» отрабатывали?

    Reply
  18. Dementor

    (17) к счастью, класс задач с чувствительностью к регистру не очень широкий.

    В тех случаях, когда нужно на формах делать регистрочувствительный фильтр — нужно делать этот фильтр самому. В зависимости от данных есть разные пути. Для небольшой выборки, когда отклонений много — запросом и повторным сравнением отобрать нужные ссылки и наложить на динамический список отбор по Ссылка ВСписке. Если выборка большая и мало отклонений — оставить Реквизит Равно/Подобно/Содержит, а с помощью функции отобрать именно отклонения для второго условия отбора — Ссылка НеВСписке.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *