Игра с огнём, или нулевой байт
В одном проекте заказчик потребовалось различать (и делать поиск) по трем состояниям текстового поля в Lucene индексе:
непустое значение (работает из коробки)
пустая строка "" (не поддерживается люсин)
null (не поддерживается люсин)
Lucene не хранит null
и пустые строки ""
- значения просто не индексируется. Для бизнес-логики, где нужно различать все три состояния, стандартных механизмов Lucene недостаточно.
Создание "специальных" замен в виде комбинаций типа "_null_" текста и спецсимволов - ломается тестерами которые пропускали различный мусор через индекс.
Был выбран компромиссный подход:
"\0"
(строка из нулевого байта) используется как маркерnull
"\0\0"
(строка из двух нулевых байтов) используется как маркер""
Пробило в холодный пот? Правильно, и меня тоже. Тем не менее, это рабочий способ.
На самом деле, строка из одиночного нулевого байта вполне нормально поддерживается в Java - главное не выпустить ее наружу. В редакторах и логах нулевой байт не виден, это требует более тщательной отладки.
Плюсы:
\0
— это валидный символ в Java-строке, который практически не встречается в реальных данных.Символ
\0
невозможно ввести напрямую из внешних систем, редакторов или форм без явного кодирования. Это защищает от случайных коллизий, даже если тестировщики пробуют «мусорные» символы.Таким образом достигается стабильное различие между
null
,""
и содержимыми строками.
Риски:
Утечки наружу. Маркеры
\0
могут попасть в API-ответы, логи, сериализацию. В нашем случае lucene был в обертке и поиск напрямую не использовался внешними системами - обработчик вызовов был инкапсулирован в прокси сервис.
Использование \0
и \0\0
как маркеров — это баланс между «желанием клиента» и технической безопасностью. Работает, но требует дисциплины: любая утечка этих символов превращает решение в источник трудноуловимых багов снаружи индекса.