Как исправить ошибку разделения пакета запросов в консоли запросов при наличии точки с запятой в тексте?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

В чем причина некорректного разделения пакета запросов?

Рассмотрим алгоритм, который традиционно используется во многих базовых и упрощенных консолях запросов для разделения текста на отдельные запросы пакета. Функция ищет символ точки с запятой с помощью простой функции Найти():


Функция ПостроитьМассивТекстовЗапросов(Знач ТекстЗапроса)
    МассивТекстов = Новый Массив;
    Пока Не ПустаяСтрока(ТекстЗапроса) Цикл
        ТочкаСЗапятой = ";";
        ПозицияТочкиСЗапятой = Найти(ТекстЗапроса, ТочкаСЗапятой);
        Если ПозицияТочкиСЗапятой = 0 Тогда
            ТекстОчередногоЗапроса = ТекстЗапроса;
            ПозицияТочкиСЗапятой = СтрДлина(ТекстЗапроса);
        Иначе    
            ТекстОчередногоЗапроса = Лев(ТекстЗапроса, ПозицияТочкиСЗапятой - 1);
        КонецЕсли;    
        Если Не ПустаяСтрока(ТекстОчередногоЗапроса) Тогда 
            МассивТекстов.Добавить(СокрЛП(ТекстОчередногоЗапроса));
        КонецЕсли;    
        ТекстЗапроса = Сред(ТекстЗапроса, ПозицияТочкиСЗапятой + 1);
    КонецЦикла;    
    Возврат МассивТекстов;
КонецФункции

Выясним причину сбоя: такой алгоритм не анализирует синтаксический контекст языка запросов 1С. Для него любой символ ; является признаком окончания очередного запроса в пакете. Однако символ точки с запятой может легитимно находиться:

  1. Внутри строкового литерала (в кавычках ""), например: ВЫБРАТЬ "Строка; с точкой" КАК Поле.
  2. Внутри однострочного комментария (после //), например: // Разделитель строк; комментарий.

Разберем основные варианты исправления и обхода этой проблемы.

Способ 1. Передача строки через параметры запроса (быстрый обход проблемы)

Если нет возможности или желания модифицировать код самой консоли, самым простым решением является вынос строковых литералов, содержащих символ ;, в параметры запроса.

Посмотрим на пример. Вместо явного указания текста в теле запроса:


ВЫБРАТЬ
    "Значение1;Значение2" КАК ПолеРазделитель

Укажем параметр и передадим строку через табличную часть параметров консоли:


ВЫБРАТЬ
    &СтрокаСРазделителем КАК ПолеРазделитель

В таблице параметров консоли устанавливаем значение параметра &СтрокаСРазделителем равным Значение1;Значение2. В этом случае парсер консоли не встретит символ ; в теле запроса и обработает пакет без ошибок.

Способ 2. Использование объекта СхемаЗапроса

Разберем профессиональный платформенный подход к разбору запросов без написания собственных строковых парсеров. Для разбора пакета запросов в платформе 1С:Предприятие 8 предусмотрен объект СхемаЗапроса.

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


Функция ПостроитьМассивТекстовЗапросов(Знач ИсходныйТекстЗапроса) Экспорт
    МассивТекстов = Новый Массив;
    
    Схема = Новый СхемаЗапроса;
    Попытка
        Схема.УстановитьТекстЗапроса(ИсходныйТекстЗапроса);
        Для Каждого ЗапросПакета Из Схема.ПакетЗапросов Цикл
            МассивТекстов.Добавить(ЗапросПакета.ПолучитьТекстЗапроса());
        КонецЦикла;
    Исключение
        // Если текст синтаксически некорректен или не может быть разобран схемой,
        // возвращаем исходный текст одним элементом для штатной обработки ошибки
        МассивТекстов.Добавить(ИсходныйТекстЗапроса);
    КонецПопытки;
    
    Возврат МассивТекстов;
КонецФункции

Важное ограничение: метод СхемаЗапроса.УстановитьТекстЗапроса() проверяет корректность синтаксиса и существование объектов метаданных. Если в запросе допущена синтаксическая ошибка, возникнет исключение, поэтому блок Попытка ... Исключение здесь обязателен.

Способ 3. Посимвольный разбор текста с учетом кавычек и комментариев (конечный автомат)

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

Разберем по шагам логику такого парсера:

  1. Обходим строку посимвольно от первого до последнего знака.
  2. При встрече двойных кавычек " переключаем флаг ВнутриСтроки, учитывая экранирование кавычек.
  3. При встрече сочетания // взводим флаг ВнутриКомментария, который сбрасывается только при переходе на новую строку (Символы.ПС).
  4. Точка с запятой считается разделителем запросов только в том случае, если оба флага равны Ложь.

Функция ПостроитьМассивТекстовЗапросов(Знач ИсходныйТекстЗапроса) Экспорт
    МассивТекстов = Новый Массив;
    
    ДлинаТекста = СтрДлина(ИсходныйТекстЗапроса);
    Если ДлинаТекста = 0 Тогда
        Возврат МассивТекстов;
    КонецЕсли;
    
    ВнутриСтроки = Ложь;
    ВнутриКомментария = Ложь;
    ПозицияНачалаТекущегоЗапроса = 1;
    Индекс = 1;
    
    Пока Индекс <= ДлинаТекста Цикл
        Символ = Сред(ИсходныйТекстЗапроса, Индекс, 1);
        СледующийСимвол = ?(Индекс < ДлинаТекста, Сред(ИсходныйТекстЗапроса, Индекс + 1, 1), "");
        
        Если ВнутриКомментария Тогда
            Если Символ = Символы.ПС Или Символ = Символы.ВК Тогда
                ВнутриКомментария = Ложь;
            КонецЕсли;
        ИначеЕсли ВнутриСтроки Тогда
            Если Символ = """" Тогда
                Если СледующийСимвол = """" Тогда
                    // Экранированная кавычка внутри литерала - пропускаем следующий символ
                    Индекс = Индекс + 1;
                Иначе
                    ВнутриСтроки = Ложь;
                КонецЕсли;
            КонецЕсли;
        Иначе
            Если Символ = "/" И СледующийСимвол = "/" Тогда
                ВнутриКомментария = Истина;
                Индекс = Индекс + 1;
            ИначеЕсли Символ = """" Тогда
                ВнутриСтроки = Истина;
            ИначеЕсли Символ = ";" Тогда
                ДлинаФрагмента = Индекс - ПозицияНачалаТекущегоЗапроса;
                Фрагмент = СокрЛП(Сред(ИсходныйТекстЗапроса, ПозицияНачалаТекущегоЗапроса, ДлинаФрагмента));
                Если Не ПустаяСтрока(Фрагмент) Тогда
                    МассивТекстов.Добавить(Фрагмент);
                КонецЕсли;
                ПозицияНачалаТекущегоЗапроса = Индекс + 1;
            КонецЕсли;
        КонецЕсли;
        
        Индекс = Индекс + 1;
    КонецЦикла;
    
    // Добавляем оставшуюся часть текста после последней точки с запятой
    Если ПозицияНачалаТекущегоЗапроса <= ДлинаТекста Тогда
        Фрагмент = СокрЛП(Сред(ИсходныйТекстЗапроса, ПозицияНачалаТекущегоЗапроса));
        Если Не ПустаяСтрока(Фрагмент) Тогда
            МассивТекстов.Добавить(Фрагмент);
        КонецЕсли;
    КонецЕсли;
    
    Возврат МассивТекстов;
КонецФункции

Способ 4. Отказ от ручного разделения через ВыполнитьПакетСПромежуточнымиДанными()

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

В платформе 1С:Предприятие 8.3 для этого предусмотрен штатный метод ВыполнитьПакетСПромежуточнымиДанными() объекта Запрос. Он избавляет от необходимости вручную делить текст на отдельные запросы:


Запрос = Новый Запрос;
Запрос.Текст = ТекстПакетаЗапросов;
// Установка параметров
Запрос.УстановитьПараметр("Организация", ВыбОрганизация);

// Выполняем весь пакет целиком за один вызов сервера
МассивРезультатов = Запрос.ВыполнитьПакетСПромежуточнымиДанными();

Для Каждого Результат Из МассивРезультатов Цикл
    Если Не Результат.Пустой() Тогда
        Выборка = Результат.Выбрать();
        // Обработка и вывод промежуточных таблиц и итогового результата
    КонецЕсли;
КонецЦикла;

Метод ВыполнитьПакетСПромежуточнымиДанными() возвращает массив объектов РезультатЗапроса для всех частей пакета, включая запросы создания временных таблиц с директивой ПОМЕСТИТЬ. Это исключает любые ошибки ручного парсинга текста.

← На главную