Хранилище значения. Заметки

06.12.23

Разработка - Групповая разработка (Git, хранилище)

Некоторые подробности про общеизвестный инструмент.

Объект "ХранилищеЗначения" известен в 1С, начиная с 8.0 и, казалось бы, прост, понятен, изучен вдоль и поперёк. Но иногда возникают вопросы, касающиеся работы с хранилищем, и данная публикация (по сути, краткий антисклерозник) призвана зафиксировать ответы на некоторые из них.


Хранилище значения (далее ХЗ) представляет собой данные, полученные кодированием и, при необходимости, сжатием входных данных с помощью 32-х битного алгоритма Deflate, он же Deflation (о размерах сжатия см.ниже).
 

Хранение

Данные типа ХЗ могут храниться и обрабатываться в таблицах СУБД, в рамках модели данных 1С являясь реквизитами шапки, табличных частей, регистров, бизнес-процессов, задач; ресурсами регистров сведений, параметрами сеанса, общими реквизитами, функциональными опциями (на основе хранимых данных типа ХЗ). То есть, ХЗ может быть типом только для поля, хранящегося в базе (т.е. в отчётах, обработках, реквизитах/параметрах форм этого нет).

С точки зрения наиболее распространённых СУБД ситуация следующая.

SQL

ХЗ хранятся в полях IMAGE (тип данных SQL2003: BLOB), это традиционный тип LOB-хранилищ для SQL Server, в котором можно хранить как текстовые, так и двоичные данные. Хранит двоичное значение переменной длины до 2 147 483 647 байт (т.е. 2 Гбайт или 2^31 байт), механика выделения места под страницу таблицы и таблицу вообще варьируется в разных версиях SQL и разных настройках. Столбцы типа IMAGE (как и родственная ей TEXT) имеют множество ограничений на способы использования - например, нельзя создавать индекс по столбцу типа IMAGE, есть особенности при работе с хранимыми процедурами. При этом, значениями типа IMAGE можно манипулировать при помощи функций DATALENGTH, PATINDEX, SUBSTRING, TEXTPTR и ТЕХTVALID, а также команд READTEXT, SET TEXTSIZE, UPDATETEXT и WRITETEXT. Преобразования типа implicit conversion (а именно на них опирается 1С) возможны из IMAGE в: binary, varbinary, timestamp (логично, сплошь двоичное по сути), в него ещё можно из char и  varchar. Сам этот тип усердно НЕ рекомендуется к использованию в скуле, но до сих пор есть. Последние несколько версий платформ для хранения ХЗ в MS SQL используют тип VARBINARY(MAX). IMAGE является устаревшим и используется по накатанной из старых версий платформ.

PostgreSQL

ХЗ хранятся в полях bytea (ссылка стандарта на тот же тип данных SQL2003: BLOB). Для хранения нужны 4 байта плюс реальный размер битовой строки (т.е. 4 бита сверх трёхбитового заголовка самого потока Deflate). В двоичных строках можно хранить байты с кодом 0 и другими «непечатаемыми» значениями (обычно это значения вне десятичного диапазона 32..126). Обрабатываются они как двоичные в чистом виде (не как текстовые, не по языковым стандартам). Тип bytea поддерживает два формата ввода и вывода: "шестнадцатеричный" и традиционный для PostgreSQL формат "спецпоследовательностей". Входные данные принимаются в обоих форматах, а формат выходных данных зависит от параметра конфигурации bytea_output; по умолчанию выбран 16-ричный. Кстати, известно, что оный формат был введён в PostgreSQL 9.0; в ранних версиях он может и не работать - интересно, учитывает ли это платформа? По утверждению ИТС, содержит значение, если его тип "Хранилище значения", или последовательность байт нулевой длины в противном случае.

Кстати, если говорить об интеграции и внутренних передачах таких значений между системами - в Visual Basic, например, эти значения преобразуются в одномерные массивы Byte(). Этот массив имеет диапазон Byte (от 0 до length 1), где length — число байтов в значениях SQL Server binary, varbinary или image.

ХЗ не может фигурировать в составных типах. Однако, есть несколько способов обойти декларативный запрет и даже использовать в функционале: хранилище может быть определяемым типом, но только одиночным значением во всём типе (т.е. не составным), и может фигурировать как тип ПВХ (через определяемый), но тоже только одиночным. Возможно, именно так проявляется необходимость объявления требуемого места в таблицах СУБД.


Переменные и формы

Данные типа ХЗ могут храниться в переменной языка 1С в контексте выполнения , т.е. размещаться в поименованной области памяти. Ограничений на использование нет ни по типам модулей (общие, глобальные, модули объектов, менеджеров, форм, команд и т.д.), ни по исполнению на клиенте/сервере. Конструируется на сервере, а как переменная живёт и на клиенте. Является одним из традиционных способов рабоче-служебной сериализации данных, в т.ч. безотносительно степени сжатия, и передачи в сжатом виде. Само понятие "сериализация" в СП 1С привязано именно к ХЗ, т.е. возможность помещения значения в ХЗ терминологически и было долгое время сериализацией. В ХЗ может быть помещено любое значение, про которое это указано в СП, и любая универсальная либо обменная коллекция, состоящая только из сериализуемых значений на любую глубину вложенности.

Программное использование ХЗ не вполне прозрачно и очевидно. Так, чтение реквизита объекта типа ХЗ выполняют традиционно в "ПриЧтенииНаСервере", а запись - в "ПередЗаписьюНаСервере", потому что в данных формы ХЗ официально вроде как нет. На самом деле, возможны варианты. ХЗ может быть присвоено реквизиту формы (в т.ч. табличному реквизиту коллекции) с типом "Произвольный". Ряд ситуаций работы с ХЗ в клиентском контексте, несмотря на утверждения СП, не компилируется, т.е. для уверенности в результате лучше всё делать на сервере. Очевидно, что не стоит работать с ХЗ как с переменной модуля формы.

Находясь в коллекциях формы, НЕ может обрабатываться на клиенте (чтение/запись), но не теряется при передаче формы на сервер и обратно, сбросе кэша, переинициализации контекста. Может передаваться как параметр формы без его явного объявления в т.ч. в другую форму; может быть ключевым параметром формы - всё это корректно работает.

В обычных формах реквизит недоступен к выводу в список и на форму, но может фигурировать как значение реквизита формы произвольного типа или через определяемый (вообще в реквизитах только через него и можно "обернуть"), и как значение колонки таблицы/дерева значений произвольного типа.
В управляемых формах аналогично. Формально, выводятся сообщения, что "в данных формы нельзя", но если обёрнуто в произвольный реквизит, то хранит, обрабатывает и показывает.

ХЗ может фигурировать как аргумент в методе обычной и упр.формы Закрыть(хз), и корректно передаётся; может и фигурировать в ОписанииОповещения как результат закрытия формы, ранее открытой с указанием такового.
Важно: между сеансами для формы с авто (а равно и не-авто) сохранением значений произвольный реквизит с таким значением НЕ сохраняет!

Современное использование ХЗ поражает нетривиальностью - так, например, в БСП значения типа ХЗ хранятся и обрабатываются в параметрах СКД отчётов, и на этом основана целая механика работы. Вопросы трансляции и исполнения СКД, в чьих полях так или иначе фигурируют ХЗ или заявлена типизация ХЗ - тема отдельного исследования.
 

Кодинг

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

Помимо сериализации, ХЗ предлагает в конструкторе управление сжатием (это нелинейно эквивалентно степеням сжатия, т.е. "быстрое", "обычное", "максимальное", а не статике/динамике по Хоффману).

Поскольку нет возможности напрямую замерить объём, занимаемый переменной 1С в памяти, рассматривать приходится строки и двоичные данные, полученные из них. ХЗ удобно обрабатывать через xml

// из строки в ХЗ
хз=XMLЗначение(Тип("ХранилищеЗначения"),ИсходнаяСтрокаBase64);
// из ХЗ в строку
ИтоговаяСтрокаBase64=XMLСтрока(ХЗ);
стро1=XMLСтрока(хз);
// эквивалентно
стро2=СериализаторXDTO.XMLСтрока(хз);
// тогда как, например, такая сериализация гораздо больше по размеру
стро3=ЗначениеВСтрокуВнутр(хз);

Важно понимать, что ХЗ это не значение, а указатель на область памяти. Они всегда разные, поэтому ни сравнение, ни поиск, ни отбор (в т.ч. в запросах) применять нельзя. Указатель равен сам себе лишь в рамках конкретного оператора-конструктора (и, кстати, в этом случае по нему работает, например, поиск в универсальных коллекциях). Так, например, если "А=хз; Б=А;", то А будет равно Б. А для сравнения именно значений следует действовать так (причём это не будет распаковкой как "расжатием"):

СодержимыеВнутриХЗРавны=(XMLСтрока(хз1)=XMLСтрока(хз2));

и это не зависит от наличия и степени сжатия в их конструкторах. Кстати, после применения конструктора определить степень сжатия можно лишь преобразованием в двоичные данные и анализом первых битов потока.

Так вот, размер "как есть" мы можем узнать, либо создав объект метаданных (например, справочник с единственным реквизитом типа ХЗ) и получив размер таблицы методом ПолучитьРазмерДанныхБазыДанных, либо опосредованно, через строку и двоичные данные (учитывая неизбежно прибавляемое), так: 

Размер=ПолучитьДвоичныеДанныеИзСтроки(XMLСтрока(хз)).Размер();
// или так
Размер=Base64Значение(XMLСтрока(хз)).Размер();

Это не годится для абсолютных замеров (так, один символ латиницы, т.е. по идее 1 байт, и символ кириллицы, 2 байта, дают одинаковые 32 байта на выходе), но годится для относительных прикидок. Так, вызов без прямого указания "СжатиеДанных" всегда даёт значение большего (и иногда существенно большего, в разы) размера, чем с любым сжатием. На малых размерах сжатие не даёт заметного эффекта при любом аргументе. В СП сказано, что аргумент по умолчанию для объекта "СжатиеДанных" равен 1, но сжатие без его указания и с явным указанием даёт разные размеры, до десятков и сотен байт. На средних объектах (например, строки 500-1000 символов) сжатие со степенью выше 3-4 даёт примерно одинаковые итоговые размеры, т.е. эффективность одинаковая. Наряду с этим, в ряде старых релизов конструктор без сжатия даёт меньший итоговый размер.

Деструкция этого указателя тоже имеет нюансы. 

// правильные способы
хз="";
хз=Неопределено;
хз=Null;
хз=Новый ХранилищеЗначения(Неопределено);
хз=Новый ХранилищеЗначения(Null);
// неправильный способ
хз=Новый ХранилищеЗначения(""); // занимает как минимум на 3 байта больше, чем остальные

В таблицах/деревьях значений с явно объявленной колонкой может использоваться везде, кроме запроса (не помещается в ВТ), не воспринимается как аргумент запроса, даже как &ПараметрТипаХЗ в секции "ВЫБРАТЬ". Что, учитывая известное про СУБД, не вполне логично.

При копировании объекта с реквизитами, содержащими ХЗ, ни интерфейсно, ни программно, эти значения НЕ наследуются, не копируются.

Если некое значение, упакованное в ХЗ, подпадает под RLS, то логика следующая - платформа всегда вначале выполняет неявную распаковку, либо рассматривает сделанную в коде явную; а затем накладывает ограничение, так что RLS применится именно для уже распакованного значения, спровоцировать тут ошибку ни разу не удалось.

Из интересных публикаций рекомендую эту.

Поскольку в современных метаданных конфигураций реквизит типа ХЗ где-нибудь точно наличествует, а также фигурирует в коде, то вопрос занимаемого им места актуален. В связи с этим было бы интересно исследовать рост места, занимаемого таблицей, состоящей исключительно из ХЗ, а также сравнить эффективность работы "СжатиеДанных" и основанного на том же Deflation объекта "ЗаписьZipФайла", пробуя разные значения УровеньСжатияZIP (естественно, при МетодСжатияZIP = "Сжатие").

 

Следует учитывать, что ряд объектов и данных 1С не подлежит сериализации в ХЗ. Ни в режиме сжатия данных, ни без него. Таковым, например, может быть компоновщик настроек СКД или иные служебные объекты СКД. В таком случае будет выдано сообщение "Переданное значение не может быть помещено в ХранилищеЗначения, поскольку не сериализуется или содержит вложенный несериализуемый элемент".

 

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

P.S. Опыты проводились на 8.3.6.2237 и 8.3.16.1224.

Хранилище значения СУБД размер данных

См. также

Системы контроля версий для 1С-разработчиков.

1С-программирование DevOps и автоматизация разработки Групповая разработка (Git, хранилище) DevOps для 1С Платформа 1С v8.3 Платные (руб)

Основы командной разработки на 1С. Использование систем контроля версий при разработке на платформе 1С:Предприятие 8

4900 руб.

29.06.2022    8802    78    4    

105

OpenYellow - рейтинг открытых GitHub репозиториев для платформы 1С:Предприятие

Групповая разработка (Git, хранилище) Бесплатно (free)

Обновляемый топ GitHub репозиториев для 1С по всем языкам программирования и еще немного рассуждений про open-source.

05.02.2024    3403    bayselonarrend    14    

57

Насколько глубок 1С-ный GitHub?

Групповая разработка (Git, хранилище) Бесплатно (free)

Open-source проекты - важная часть мира программного обеспечения. 1С привычно держится немного в стороне от глобальных трендов, но бросить холодный статистический взгляд на положение дел мне показалось небезынтересным.

22.01.2024    7460    bayselonarrend    50    

86

TCP прокси-сервер хранилища конфигурации 1С

DevOps и автоматизация разработки Групповая разработка (Git, хранилище) OneScript Платформа 1С v8.3 Бесплатно (free)

Продолжение истории с прокси хранилища, но уже не на HTTP, а на TCP и без падений по памяти веб-сервера. Проверяем комментарии хранилища, вызываем веб-хуки, старты пайплайнов, gitsync по событию помещения версии в хранилище. И все это полностью на знакомом и понятном OneScript.

17.01.2024    2424    kamisov    17    

55

Отдай корень! Библиотека OneScript для получения информации о захваченных объектах в хранилище

Групповая разработка (Git, хранилище) Бесплатно (free)

Хранилище конфигурации 1С - это инструмент групповой разработки. Работают с хранилищем следующим образом: захватывают какой-либо объект, редактируют, потом отдают его в хранилище. Хранилище помечает уже захваченные объекты и не дает возможности захватить их другим пользователям. Это рождает и самый большой недостаток хранилища - невозможность работы с одним объектом нескольких пользователей, например в случае доработки разных методов в одном большом модуле. Корень конфигурации - это самый верхний ее узел. Только захватив корень, мы можем добавить в конфигурацию новые общие модули, документы, справочники, регистры и подобное. Только захватив корень можно изменить настройки поддержки конфигурации. Соответственно, если корень захвачен одним программистом, другой программист не может добавить новые объекты или снять что-то с поддержки. Потому то и всплывает эта фраза - отдай корень, мне нужно тоже что-то добавить.

26.12.2023    1156    ardn    1    

24

Git Code Review - инструмент для рецензирования кода

Групповая разработка (Git, хранилище) Платформа 1С v8.3 Конфигурации 1cv8 1С:ERP Управление предприятием 2 Абонемент ($m)

Git Code Review - инструмент, позволяющий быстро анализировать изменения из git-репозитория прямо в 1С

1 стартмани

20.12.2023    3573    52    salexdv    26    

76

Захват в хранилище по составу подсистем

Групповая разработка (Git, хранилище) Управляемые формы 8.3.8 Конфигурации 1cv8 Абонемент ($m)

Обработка для захвата объектов в хранилище согласно составу подсистем.

1 стартмани

21.11.2023    1243    5    ImHunter    0    

16

Работа с GitLab API

Групповая разработка (Git, хранилище) WEB-интеграция Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Работа с API GitLab на примере запуска pipeline с переменными, отслеживания его статуса и загрузкой полученных артефактов.

1 стартмани

06.10.2023    1004    2    itsys    0    

6
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. SlavaKron 03.11.20 09:20 Сейчас в теме
РазмерХЗПлюс=ПолучитьДвоичныеДанныеИзСтроки(XMLСтрока(хз)).Размер();
Это явно не правильно. Отсюда и проблемы, о которых вы говорите.
Размер ХЗ без учета размера описания ХЗ или просто размер сжатых данных:
Размер = Base64Значение(XMLСтрока(ХЗ)).Размер();
2. Yashazz 4692 03.11.20 10:06 Сейчас в теме
(1) Этот вариант тоже не даёт правильные данные, проверял. Но да, чего-то не внёс в статью; ща внесу.
3. Yashazz 4692 03.11.20 10:10 Сейчас в теме
Вообще очень не хватает нормального sizeof, применяемого как к пустой типизированной, так и к наполненной переменной... Много интересного узнали бы.
FatPanzer; +1 Ответить
4. papa_harlo 153 05.11.20 14:17 Сейчас в теме
Эх... мне бы Вашу статью неделю назад увидеть)))
может подскажите:
сохраняю картинку jpg в хранилище значений без сжатия.
селектом в MS SQL получаю запись, значение моей картинки содержит сигнатуру вида "0x0101........"
посмотрел здесь https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D1%81%D0%B8%D­0%B3%D0%BD%D0%B0%D1%82%D1%83%D1%80_%D1%84%D0%B0%D0%B9%D0%BB%­D0%BE%D0%B2
такой сигнатуры не существует. так и не понял как не используя 1С "вытянуть" данные из записи((((

(3)
Прикрепленные файлы:
5. papa_harlo 153 05.11.20 14:18 Сейчас в теме
7. SlavaKron 05.11.20 15:04 Сейчас в теме
(4)
не используя 1С "вытянуть" данные из записи

Если версия MS SQL 2016 и выше, можно использовать функцию DECOMPRESS. Для хранилища значений:
SEL ECT
    DECOMPRESS(0x1F8B0800000000000400 + SUBSTRING(DataFieldName, 19, LEN(DataFieldName) - 18)) as Data
FR OM
    MyTable

Еще можно тут почитать: https://infostart.ru/public/1125887/
dmarenin; NittenRenegade; YPermitin; DrAku1a; Drivingblind; Yashazz; papa_harlo; +7 Ответить
8. Yashazz 4692 05.11.20 16:45 Сейчас в теме
(7) Во! Спасибо, мил человек. Теоретически я это знал, а вот пристреляться по позициям всё времени не было. Спасибище!
6. aspirator23 339 05.11.20 14:29 Сейчас в теме
9. asved.ru 36 06.11.20 07:31 Сейчас в теме
ХЗ не может фигурировать в составных типах. Однако, есть несколько способов обойти


Увижу в коде - убью.
Если со стороны платформы что-то запрещено и причины этого вам не понятны, то это не необоснованный запрет, а недостаток знаний у Вас.
Поручик; +1 Ответить
11. Yashazz 4692 06.11.20 17:24 Сейчас в теме
(9) Ну лично мне-то причины понятны) И да, соглашусь - я за такое в реальном коде без прям жёсткой необходимости - тоже убивал бы.
Поручик; +1 Ответить
10. SerVer1C 746 06.11.20 08:57 Сейчас в теме
Последние несколько версий платформ для хранения ХЗ в MS SQL используют тип VARBINARY(MAX). IMAGE является устаревшим и используется по накатанной из старых версий платформ.
12. PythonJ 111 24.11.20 00:03 Сейчас в теме
В СП сказано, что аргумент по умолчанию для объекта "СжатиеДанных" равен 1, но сжатие без его указания и с явным указанием даёт разные размеры, до десятков и сотен байт.

В СП сказано:
Целое число в диапазоне -1...9. -1 - степень сжатия по умолчанию. 0 - никакого сжатия, 9 - максимальная степень сжатия.
Значение по умолчанию: -1.

-1 - не степень сжатия как таковая, а "степень сжатия по умолчанию для алгоритма Deflation", она не обязана быть равной 1 или любой другой из диапазона 1..9.
13. PlatonStepan 38 11.12.20 08:38 Сейчас в теме
(12) Вроде как по-умолчанию было 6, а сейчас -1.

(0)
хз=Новый ХранилищеЗначения(Значение, Новый СжатиеДанных()) - насколько я понял, содержимое предваряется BOM (EF BB BF) - byte order mark, а уже потом сжимается.
Из-за этого разобрать содержимое в сторонних программах не всегда получается, даже если избавиться от описания сжатия данных обрезав первые 18 байт.

или что-то не так делаю?

хз = Новый ХранилищеЗначения(ДанныеДляСжатия, Новый СжатиеДанных(9));
//Через сериализацию получим строку Base64
Base64Строка_ = XMLСтрока(хз);

БуферДвоичныхДанных_ = ПолучитьБуферДвоичныхДанныхИзBase64Строки(Base64Строка_);

//отсекаем описание сжатия данных 1с
БуферДвоичныхДанных_ = БуферДвоичныхДанных_.ПолучитьСрез(18);

Base64Строка_Deflate = ПолучитьBase64СтрокуИзБуфераДвоичныхДанных(БуферДвоичныхДанных_);
Показать
14. Al-X 03.02.22 15:13 Сейчас в теме
Добрый день ! А подскажите, если есть у меня регистр сведений, и у него в реквизит типа ХранилищеЗначений. Будет ли автоматически удаляться данные ХЗ из базы данных, если удалить запись регистра значений ?
15. Yashazz 4692 03.02.22 15:24 Сейчас в теме
(14) Если там хранится ссылка на некое значение, то, конечно, ничего ниоткуда не удалится. Для ХЗ ссылочная целостность не поддерживается, так же, как и для любых сериализованных данных.
Поручик; +1 Ответить
16. Serggray 14 28.03.23 15:39 Сейчас в теме
(15) Хорошо, если не автоматически , то как можно очистить этот реквизит , чтобы очистилось ХранилищеЗначений занимаемое им.
К примеру такая конструкция не срабатывает
	zn = ЗаписьРегистра.ХранимыйФайл.Получить() ;
	zn = Новый ХранилищеЗначения(Неопределено) ;
	ЗаписьРегистра.ХранимыйФайл = zn ;
Оставьте свое сообщение