Почему перестало работать сравнение идентификаторов документов в 1SQLite и как исправить совместимость таблиц

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При использовании внешней компоненты 1SQLite в среде 1С:Предприятия 7.7 разработчики нередко сталкиваются с ситуацией, когда после обновления версии библиотеки с 1.0.2.3 или 1.0.2.4 на 1.0.2.6 перестают корректно отрабатывать условия точного равенства по полям документов или справочников. При этом частичный поиск через LIKE продолжает возвращать строки, а попытка вернуть прежнюю версию компоненты приводит к ошибке «file is encrypted or is not a database». Разберем детально, почему это происходит и как правильно решить возникшие проблемы.

Анализ проблемы: почему перестало работать точное сравнение

Рассмотрим типовой сценарий записи и последующего поиска ссылки на документ, в котором проявляется проблема:


ЗапросSQLite.Подставлять("ТекВремя", ТекущееВремя());
ЗапросSQLite.Подставлять("ТекДата", ТекущаяДата());
ЗапросSQLite.Подставлять("ТекДок", Док); 

ТекстЗапроса = "
|INSERT INTO Sklad (time, date, given, doc) VALUES (:ТекВремя, :ТекДата, 0, :ТекДок);
|";
ЗапросSQLite.Выполнить(ТекстЗапроса);

// Попытка проверки записи
ЗапросSQLite.Подставлять("НачДатаПров", ТекущаяДата() - 45);
ЗапросSQLite.Подставлять("КонДатаПров", ТекущаяДата());
ЗапросSQLite.Подставлять("ПроверДок", Док);

ТекстЗапросаПров = "
|SELECT
|    Sklad.doc as [Реализация $Документ.Реализация]
|FROM 
|    Sklad
|WHERE
|    (Sklad.date between :НачДатаПров and :КонДатаПров) 
|    and (Sklad.doc = :ПроверДок) 
|";

Выясним причину, по которой условие (Sklad.doc = :ПроверДок) перестает находить данные в новой версии движка. Причина кроется в определении структуры таблицы и механизме типизации (Type Affinity) в SQLite:

  1. Формат идентификаторов в 1С 7.7: Идентификаторы документов и элементов справочников (IDDOC, ID) представляют собой строки в 36-ричной системе счисления длиной до 9 символов, дополненные пробелами. Например: ' 1E600 ' или ' 1E4R6 '.
  2. Ошибочное назначение типа колонки: Если при создании таблицы поле под идентификатор было ошибочно объявлено как doc INTEGER, движок SQLite применяет к нему числовое сродство (INTEGER affinity). При вставке строковых значений SQLite не блокирует операцию, но при выполнении операторов сравнения (=, <, >) предпринимает попытку преобразовать обе стороны выражения в числовой тип.
  3. Экспоненциальная запись и переполнение: В строке ' 1E600 ' присутствуют цифры и латинская литера E. Начиная с версии движка SQLite, используемой в 1SQLite 1.0.2.6, расширен парсер распознавания чисел. Строка 1E600 воспринимается парсером как число с плавающей точкой в научной нотации (1, умноженное на 10 в 600-й степени). Так как стандарт IEEE 754 (double precision) ограничен значением порядка 1.79E+308, происходит переполнение до бесконечности (+Infinity), и операция сравнения на точное равенство всегда возвращает ложь. Идентификаторы вида ' 1E4R6 ' не могут быть преобразованы в число из-за буквы R, поэтому остаются строками и сравниваются без сбоев.
  4. Поведение оператора LIKE: Запрос с условием Sklad.doc LIKE '%'||:ПроверДок||'%' работает даже при типе INTEGER, поскольку оператор LIKE принудительно преобразует операнды к строковому представлению (TEXT affinity), полностью игнорируя попытки распарсить число с плавающей точкой.

Способ 1. Исправление структуры таблицы и типизации

Главным и фундаментально правильным решением является изменение типа колонки с идентификатором. Все колонки, предназначенные для хранения ссылок 1С, обязаны иметь текстовый тип.

  1. Объявляем колонку с типом TEXT, VARCHAR или CHAR(9):
    
    CREATE TABLE Sklad (
        time TEXT,
        date TEXT,
        given INTEGER,
        doc CHAR(9)
    );
    
  2. Если пересоздать существующую таблицу сразу невозможно, в запросах необходимо использовать явное приведение типа с помощью CAST:
    
    SELECT
        Sklad.doc as [Реализация $Документ.Реализация]
    FROM 
        Sklad
    WHERE
        (Sklad.date between :НачДатаПров and :КонДатаПров) 
        and (CAST(Sklad.doc AS TEXT) = CAST(:ПроверДок AS TEXT));
    
  3. При создании индексов по полям с идентификаторами полезно указывать правило сортировки платформы 1С: CREATE INDEX idx_Sklad_doc ON Sklad (doc COLLATE _1C); Сравнение с COLLATE _1C игнорирует различия в лидирующих пробелах при строковом сопоставлении ID 1С.

Способ 2. Устранение ошибки «file is encrypted or is not a database» при откате

Если после перехода на 1SQLite 1.0.2.4 или 1.0.2.6 потребовалось вернуться на версию 1.0.2.3, старая версия компоненты не сможет открыть базу данных и выдаст сообщение о повреждении или шифровании файла. Это связано с тем, что новый движок SQLite переводит заголовок файла в режим поддержки расширенных возможностей (например, WAL-журналирования), устанавливая в байтах смещения 0x12 и 0x13 заголовка значение 0x02 вместо 0x01.

Рассмотрим два способа возврата совместимости файла базы данных:

  1. Штатный способ через PRAGMA (рекомендуемый): Перед возвратом на старую библиотеку необходимо открыть базу в новой версии (1.0.2.4 или 1.0.2.6) и выполнить принудительный возврат к стандартному механизму отката транзакций: ЗапросSQLite.Выполнить("PRAGMA journal_mode = DELETE;"); Команда переводит журнал в классический режим и автоматически сбрасывает флаги версии формата в заголовке файла в значение 1.
  2. Ручная правка заголовка через HEX-редактор: Если доступ к новой компоненте уже отсутствует, открываем файл базы данных в любом шестнадцатеричном редакторе. Переходим по адресам 0x12 и 0x13 (18-й и 19-й байты от начала файла) и изменяем их значения с 02 на 01. После сохранения изменений библиотека 1SQLite версии 1.0.2.3 сможет успешно открыть файл.

Способ 3. Оптимизация производительности и устранение медленной работы JOIN

Падение скорости выполнения запросов после обновления движка SQLite обычно вызвано изменением логики оптимизатора запросов (Next Generation Query Planner). При соединении таблиц 1С с локальными таблицами SQLite оптимизатор без явных индексов может ошибочно выбирать полное сканирование (Full Scan) вместо поиска по индексу.

Проанализируем шаги для восстановления высокой скорости работы:

  1. Создание отсутствующих индексов: Обязательно индексируем все поля, участвующие в соединениях JOIN и секциях WHERE: CREATE INDEX idx_Sklad_date_doc ON Sklad (date, doc);
  2. Фиксация порядка обхода через CROSS JOIN: Если планировщик некорректно меняет таблицы местами, используем CROSS JOIN вместо INNER JOIN. В SQLite оператор CROSS JOIN жестко предписывает движку обходить таблицы строго в том порядке, в котором они перечислены в тексте запроса.
  3. Отключение неоптимального индекса: Чтобы запретить оптимизатору использовать ошибочный индекс в условиях сравнения, можно применить унарный плюс: WHERE +Sklad.doc = :ПроверДок Это выражение лишает оптимизатор возможности использовать индекс по полю doc, если он приводит к деградации плана выполнения.
← На главную