2025, Oct 18 03:15
Проблема с токенизацией: TOP в T‑SQL и замена на LIMIT в Databricks SQL
Разбираем, почему токенизатор sqlparse не видит TOP в T‑SQL, из‑за чего срывается преобразование в Databricks SQL. Покажем, как надёжно менять TOP на LIMIT.
Преобразование T‑SQL в Databricks SQL часто кажется простым — пока не вмешается токенизация. Частая проблема — предложение TOP: используемый вами токенизатор не распознаёт его как ключевое слово, из‑за чего управление так и не переходит в ветку преобразования. Если ваша логика завершает работу, когда встречает только «обычные» ключевые слова, можно ненароком вернуть исходный запрос вместо ожидаемой замены TOP на LIMIT.
Постановка проблемы
Идея такова: разобрать SQL, «сплющить» токены и продолжать только при появлении редкого ключевого слова. Если всё выглядит «типично», код возвращает входную строку без изменений, откладывая преобразования. На практике это мешает обработке TOP, потому что токенизатор не помечает его как ключевое слово.
ast_unit = sqlparse.parse(input_sql)[0]
stream = TokenList(ast_unit.tokens).flatten()
shared_kw = ["SELECT","FROM","DISTINCT","WHERE","GROUP BY","ORDER BY","AS","JOIN","VALUES","INSERT INTO","UPDATE","DELETE FROM","SET","COUNT","AVG","MIN","MAX","SUM"]
passthrough = True
for piece in stream:
    if piece.ttype == Keyword:
        if piece.value.upper() not in shared_kw:
            passthrough = False
            break
if passthrough:
    return input_sqlОжидаемый следующий шаг — преобразовать TOP в LIMIT. Вместо этого функция «проскальзывает» и возвращает исходный запрос, потому что защитный флаг так и не переключается.
Причина
sqlparse.token.Keyword распознаёт ключевые слова из DDL и DML, но не из DQL. Поэтому TOP вообще не помечается как Keyword, и проверка по вашему списку разрешённых слов не срабатывает. Итог тонкий: цикл не видит «необычного ключевого слова», не делает break, и код завершает работу раньше времени.
Исправление и обновлённый код
Чтобы конвейер продолжал работу при встрече конструкций, выпадающих из охвата Keyword у токенизатора, добавьте явную проверку сырого SQL на наличие TOP и других важных для вас маркеров, например символа @.
ast_unit = sqlparse.parse(input_sql)[0]
stream = TokenList(ast_unit.tokens).flatten()
shared_kw = ["SELECT","FROM","DISTINCT","WHERE","GROUP BY","ORDER BY","AS","JOIN","VALUES","INSERT INTO","UPDATE","DELETE FROM","SET","COUNT","AVG","MIN","MAX","SUM"]
passthrough = True
for chunk in input_sql:
    if "TOP" in chunk or "@" in chunk:
        passthrough = False
if passthrough:
    for piece in stream:
        if piece.ttype == Keyword:
            if piece.value.upper() not in shared_kw:
                passthrough = False
                break
if passthrough:
    return input_sql
# continue with transformation steps (e.g., handling TOP -> LIMIT) when passthrough is FalseТак вы гарантируете, что запросы с TOP не будут возвращаться без изменений, и последующая логика сможет заменить TOP на LIMIT, как и задумывалось. Для справки: список слов, функций и символов, которые sqlparse.token.Keyword считает ключевыми, доступен здесь: https://github.com/andialbrecht/sqlparse/blob/master/sqlparse/keywords.py.
Почему это важно
При создании автоматизированных инструментов миграции SQL управление часто завязано на классах токенов и распознавании ключевых слов. Если токенизатор исключает некоторые категории, ваш инструмент может тихо пропускать критичные переписывания. Понимание того, что именно токенизатор помечает как Keyword, помогает избежать неверных предположений и сломанных цепочек преобразований.
Практические выводы
Во‑первых, проверьте, что именно выдаёт ваш токенизатор, прежде чем навешивать на него бизнес‑правила. При необходимости дополняйте проверки по токенам простыми строковыми поисками для конструкций, которые нужно обязательно ловить. Во‑вторых, когда поведение расходится с ожиданиями, трассируйте значения и путь исполнения с помощью отладочного вывода print, чтобы увидеть, что реально происходит во время выполнения, и понять, распознан ли токен вообще.
Иными словами, в такой конфигурации TOP не появится как Keyword, поэтому проверяйте его явно и позвольте остальной части конвейера выполнить замену на LIMIT. Держите список разрешённых слов компактным, знайте ограничения токенизатора и подтверждайте логику лёгкой диагностикой, прежде чем масштабировать преобразования.