53.3. Этап разбора
Этап разбора разделяется на две части:
Разбор, алгоритм которого описан в
gram.y
иscan.l
, а программный код генерируется инструментами Unix bison и flex.Преобразование, в процессе которого модифицируются и дополняются структуры данных, полученные после разбора запроса.
53.3.1. Разбор
При разборе проверяется сначала синтаксис строки запроса (поступающей в виде неструктурированного текста). Если он правильный, строится дерево запроса и передаётся дальше, в противном случае возвращается ошибка. Лексический и синтаксический анализ реализован с применением хорошо известных средств Unix bison и flex.
Лексическая структура определяется в файле scan.l
и описывает идентификаторы, ключевые слова SQL и т. д. Для каждого найденного ключевого слова или идентификатора генерируется символ языка, который затем передаётся синтаксическому анализатору.
Синтаксис языка определён в файле gram.y
в виде набора грамматических правил и действий, которые должны выполняться при срабатывании правил. Для построения дерева разбора используется код действий (это действительно код на C).
Файл scan.l
преобразуется в программу на C scan.c
с помощью flex, а gram.y
— в gram.c
с помощью bison. После этих преобразований исполняемый код анализатора создаётся обычным компилятором C. Никогда не вносите коррективы в сгенерированные файлы C, так как они будут перезаписаны при следующем вызове flex или bison.
Примечание
Упомянутые преобразования и компиляция обычно производятся автоматически сборочными файлами Makefile, поставляемыми в составе дистрибутива Postgres Pro.
Подробное описание bison и грамматических правил в gram.y
выходит за рамки данной главы. Узнать больше о flex и bison можно из книг и документации. Изучение грамматики, описанной в gram.y
, следует начать со знакомства с bison, иначе будет трудно понять, что там происходит.
53.3.2. Преобразование
На этой стадии дерево разбора создаётся только с фиксированными знаниями о синтаксической структуре SQL. При его создании не просматриваются системные каталоги, что не даёт возможность понять конкретную семантику запрошенной операции. После этого выполняется процедура преобразования, которая принимает дерево разбора от анализатора и выполняет семантический анализ, необходимый для понимания, к каким именно таблицам, функциям и операторам обращается запрос. Структура данных, которая создаётся для представления этой информации, называется деревом запроса.
Синтаксический разбор отделён от семантического анализа, потому что обращаться к системным каталогам можно только внутри транзакции, а начинать транзакцию сразу после получения строки с запросом нежелательно. Синтаксического разбора достаточно, чтобы распознать команды управления транзакциями (BEGIN
, ROLLBACK
и т. д.), поэтому их можно выполнить без дальнейшего анализа. Убедившись, что мы имеем дело с собственно запросом (например, SELECT
или UPDATE
), можно начинать транзакцию, если она ещё не начата. Только после этого можно переходить к процедуре преобразования.
Дерево запроса, создаваемое процедурой преобразования, по структуре во многом похоже на дерево разбора, но отличается во многих деталях. Например, узел FuncCall
в дереве разбора представляет то, что по синтаксису похоже на вызов функции. Этот узел может быть преобразован в узел FuncExpr
или Aggref
в зависимости от того, какой (обычной или агрегатной) окажется функция с заданным именем. Кроме того, в дерево запроса добавляется информация о фактических типах данных столбцов и результатов выражений.
53.3. The Parser Stage
The parser stage consists of two parts:
The parser defined in
gram.y
andscan.l
is built using the Unix tools bison and flex.The transformation process does modifications and augmentations to the data structures returned by the parser.
53.3.1. Parser
The parser has to check the query string (which arrives as plain text) for valid syntax. If the syntax is correct a parse tree is built up and handed back; otherwise an error is returned. The parser and lexer are implemented using the well-known Unix tools bison and flex.
The lexer is defined in the file scan.l
and is responsible for recognizing identifiers, the SQL key words etc. For every key word or identifier that is found, a token is generated and handed to the parser.
The parser is defined in the file gram.y
and consists of a set of grammar rules and actions that are executed whenever a rule is fired. The code of the actions (which is actually C code) is used to build up the parse tree.
The file scan.l
is transformed to the C source file scan.c
using the program flex and gram.y
is transformed to gram.c
using bison. After these transformations have taken place a normal C compiler can be used to create the parser. Never make any changes to the generated C files as they will be overwritten the next time flex or bison is called.
Note
The mentioned transformations and compilations are normally done automatically using the makefiles shipped with the Postgres Pro source distribution.
A detailed description of bison or the grammar rules given in gram.y
would be beyond the scope of this manual. There are many books and documents dealing with flex and bison. You should be familiar with bison before you start to study the grammar given in gram.y
otherwise you won't understand what happens there.
53.3.2. Transformation Process
The parser stage creates a parse tree using only fixed rules about the syntactic structure of SQL. It does not make any lookups in the system catalogs, so there is no possibility to understand the detailed semantics of the requested operations. After the parser completes, the transformation process takes the tree handed back by the parser as input and does the semantic interpretation needed to understand which tables, functions, and operators are referenced by the query. The data structure that is built to represent this information is called the query tree.
The reason for separating raw parsing from semantic analysis is that system catalog lookups can only be done within a transaction, and we do not wish to start a transaction immediately upon receiving a query string. The raw parsing stage is sufficient to identify the transaction control commands (BEGIN
, ROLLBACK
, etc), and these can then be correctly executed without any further analysis. Once we know that we are dealing with an actual query (such as SELECT
or UPDATE
), it is okay to start a transaction if we're not already in one. Only then can the transformation process be invoked.
The query tree created by the transformation process is structurally similar to the raw parse tree in most places, but it has many differences in detail. For example, a FuncCall
node in the parse tree represents something that looks syntactically like a function call. This might be transformed to either a FuncExpr
or Aggref
node depending on whether the referenced name turns out to be an ordinary function or an aggregate function. Also, information about the actual data types of columns and expression results is added to the query tree.