Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 22 мин
Средний
query-lifecyclesemantic-analysistype-checkinganalysis

Семантический анализ: имена и типы

Парсер построил AST — дерево синтаксиса. Дерево корректно по форме, но движок про него почти ничего не знает по смыслу. В узле Identifier написано name — но что это? Столбец какой таблицы? Какого типа? В узле Comparison > сравниваются два операнда — а можно ли их вообще сравнивать? На эти вопросы парсер ответить не мог. Отвечает следующий этап — семантический анализ.

Семантический анализ — это проверка смысла запроса и насыщение AST информацией, которой в нём не было. Этот урок разбирает две его главные задачи: разрешение имён и проверку типов, а также их результат — модель Analysis.


Граница: форма против смысла

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

Парсер проверяет форму, анализ — смысл
ПарсерСинтаксис: построен ли запрос по грамматике SQL. Результат — AST.
AST
Семантический анализСмысл: существуют ли имена, сходятся ли типы. Результат — модель Analysis.

Запрос SELECT no_column FROM no_table пройдёт парсер — он грамматически правилен. Но он бессмыслен: ни таблицы, ни столбца не существует. Поймать это — задача семантического анализа. Анализ берёт AST и проверяет каждый его узел на соответствие реальности.


Разрешение имён

Первая задача — разрешение имён (name resolution). В AST имена лежат как простые строки. Анализ должен связать каждое имя с конкретной сущностью.

Разрешение имён таблиц. Каждую ссылку на таблицу анализ должен сопоставить с реальной таблицей. Здесь работает трёхуровневая модель catalog.schema.table из третьего модуля. Если в запросе таблица написана полным именем — анализ проверяет её существование напрямую. Если коротким (customer вместо tpch.sf1.customer) — анализ достраивает имя из catalog и schema по умолчанию текущей сессии, и затем проверяет. Чтобы узнать, существует ли таблица, анализ обращается к коннектору — к сервису Metadata, который мы разбирали в модуле про коннекторы. Именно Metadata отвечает: есть такая таблица или нет.

Разрешение имён столбцов. Каждую ссылку на столбец анализ привязывает к конкретному столбцу конкретной таблицы. Здесь возникают типичные ошибки:

  • Столбца нет ни в одной из таблиц запроса — ошибка «column not found».
  • Столбец с таким именем есть в нескольких таблицах join-а, а из какой именно брать — не указано. Имя неоднозначно (ambiguous), и анализ это отвергает.
-- Столбец name есть и в customer, и в supplier — имя неоднозначно
SELECT name
FROM tpch.sf1.customer AS c
JOIN tpch.sf1.supplier AS s ON c.nationkey = s.nationkey;
Query failed: line 1:8: Column 'name' is ambiguous

Лечится квалификацией — указанием, из какой таблицы: c.name или s.name. Тогда имя разрешается однозначно.

Разрешение алиасов и областей видимости. Анализ учитывает алиасы (customer AS c), области видимости подзапросов, видимость столбцов из CTE. Какие имена доступны в данной точке запроса — это вопрос области видимости (scope), и анализ её аккуратно отслеживает, спускаясь по дереву.

NOTE

Разрешение имён — это место, где этап анализа обращается наружу, к коннекторам. Парсер был полностью автономен: ему хватало текста и грамматики. Анализу нужно знать реальную структуру источников — а её знает только коннектор через сервис Metadata. Поэтому скорость анализа частично зависит от того, как быстро коннектор отдаёт метаданные.


Проверка типов

Вторая задача — проверка типов (type checking). У каждого столбца есть тип, у каждого литерала есть тип, и каждая операция предъявляет требования к типам своих операндов. Анализ проверяет, что эти требования выполнены, и заодно вычисляет тип каждого выражения.

Тип выражения вычисляется снизу вверх по дереву. У листьев тип известен: столбец acctbal имеет тип из метаданных таблицы (скажем, double), литерал 1000 — целочисленный тип. Поднимаясь к родителю, анализ вычисляет тип составного выражения из типов потомков и проверяет, что операция к этим типам применима.

Вывод типов снизу вверх
Comparison > : booleanТип всего сравнения — boolean. Проверено: операнды сравнимы между собой.
тип вычислен из потомков
acctbal : doubleТип листа известен из метаданных таблицы — столбец acctbal имеет тип double.
1000 : integerТип литерала определяется его формой — целое число даёт целочисленный тип.

Когда типы операндов не совпадают, но операция в принципе осмысленна, анализ вставляет неявное приведение типов (implicit coercion). В сравнении acctbal > 1000 слева double, справа integer. Trino не отвергает запрос — он по правилам приведения расширяет integer до double (целое всегда представимо как вещественное без потерь) и сравнивает два double. Приведение работает, когда оно безопасно и однозначно: integer к bigint, integer к double, более узкий decimal к более широкому.

Когда же типы несовместимы и осмысленного приведения нет — анализ отвергает запрос с ошибкой типов:

-- Нельзя сложить число и произвольную строку
SELECT acctbal + name FROM tpch.sf1.customer;
Query failed: line 1:8: Cannot apply operator: double + varchar(25)

Сообщение точно называет проблему: оператор + не определён для пары double и varchar. Безопасного приведения строки к числу нет, поэтому запрос отклоняется здесь, на анализе.

WARNING

Неявное приведение удобно, но у него есть цена для производительности. Если в фильтре по большому столбцу приведение применяется к самому столбцу (а не к литералу), это может помешать pushdown и заставить движок вычислять приведение для каждой строки. Хорошая практика — писать литералы в типе столбца, чтобы приводился литерал (один раз), а не столбец (миллионы раз).


Результат: модель Analysis

Парсинг выдавал AST. Семантический анализ выдаёт Analysis — модель, в которой к структуре запроса добавлены все установленные смысловые факты.

AST после парсинга «знал» только форму: вот узел Identifier со строкой name. Analysis знает гораздо больше: этот name — столбец name таблицы tpch.sf1.customer, его тип varchar(25); это сравнение имеет тип boolean; сюда вставлено приведение integer к double; в запросе участвуют такие-то таблицы и столбцы.

От AST к обогащённой модели Analysis
ASTПосле парсера: структура запроса, но имена — просто строки, типы неизвестны.
семантический анализ
AnalysisСтруктура плюс смысл: имена разрешены в сущности, типы выведены, приведения расставлены.

Зачем это нужно следующему этапу. Планировщик, который строит логический план, не может работать с «голым» AST: чтобы построить корректный план, ему нужно точно знать, к какой таблице обращается каждая ссылка, какого типа каждое выражение, где приведения. Всю эту информацию поставляет Analysis. Семантический анализ — это мост между «синтаксически разобранным запросом» и «запросом, готовым к планированию».

И ещё одно свойство этапа: к началу планирования запрос либо признан полностью осмысленным, либо уже отклонён. Планировщик и распределённое исполнение никогда не сталкиваются с несуществующими столбцами или несовместимыми типами — анализ гарантирует, что дальше идёт только корректный запрос. Это разделение ответственности упрощает все последующие этапы.


Место анализа в жизненном цикле

Жизненный цикл — это последовательность преобразований представления запроса. Текст стал AST на парсинге. AST стал Analysis на семантическом анализе — это второй этап. Дальше Analysis станет логическим планом — деревом PlanNode, и это тема следующего урока. Каждый этап обогащает или перестраивает представление, приближая запрос к исполнимой форме.


Попробуй сам

На кластере Trino отработайте обе задачи семантического анализа:

  1. Разрешение имён. Сделайте join tpch.sf1.customer и tpch.sf1.supplier и выберите name без квалификации таблицей. Получите ошибку неоднозначности. Затем квалифицируйте имя (c.name) и убедитесь, что запрос проходит.
  2. Несуществующий столбец. Выполните SELECT no_such_col FROM tpch.sf1.nation. Это ошибка разрешения имени, а не синтаксиса.
  3. Несовместимые типы. Выполните SELECT name + 1 FROM tpch.sf1.nation. Прочитайте сообщение: оператор не определён для этой пары типов.
  4. Неявное приведение. Выполните SELECT * FROM tpch.sf1.customer WHERE acctbal > 1000 (где acctbaldouble, а 1000 — целое). Запрос работает — анализ молча вставил безопасное приведение integer к double. Подумайте, почему это приведение безопасно, а строка-в-число — нет.

Проверка знанийKnowledge check
Какие две главные задачи решает семантический анализ, и почему именно на этом этапе движок впервые обращается к коннекторам?
ОтветAnswer
Семантический анализ решает две задачи. Первая — разрешение имён: каждую ссылку на таблицу он сопоставляет с реальной таблицей (достраивая короткое имя до catalog.schema.table из контекста сессии), а каждую ссылку на столбец привязывает к конкретному столбцу конкретной таблицы, отлавливая несуществующие и неоднозначные имена. Вторая — проверка типов: анализ вычисляет тип каждого выражения снизу вверх по дереву, проверяет, что каждая операция применима к типам своих операндов, вставляет безопасные неявные приведения (например, integer к double) и отвергает запрос при несовместимых типах. Движок впервые обращается к коннекторам именно здесь, потому что для разрешения имён нужно знать реальную структуру источников — какие есть таблицы и столбцы, какого они типа. Эту структуру знает только коннектор через сервис Metadata. Парсер был автономен — ему хватало текста и грамматики, а анализу нужны внешние метаданные. Результат этапа — модель Analysis: структура запроса, обогащённая разрешёнными именами, выведенными типами и расставленными приведениями, которую дальше использует планировщик.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Почему именно на этапе семантического анализа движок впервые обращается к коннекторам, тогда как парсер этого не делал?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6