openGauss源码解析(89)
openGauss源码解析:SQL引擎源解析(4)
6.2.2 语法分析
openGuass中定义了bison工具能够识别的语法文件gram.y,同样在Makefile中可以通过bison工具对gram.y进行编译,生成gram.cpp文件。
在openGauss中,根据SQL语言的不同定义了一系列表达Statement的结构体(这些结构体通常以Stmt作为命名后缀),用来保存语法分析结果。以SELECT查询为例,它对应的Statement结构体如下。
typedef struct SelectStmt {
NodeTag type; // 节点类型
List* distinctClause; // DISTINCT子句
IntoClause* intoClause; // SELECT INTO子句
List* targetList; // 目标属性
List* fromClause; // FROM子句
Node* whereClause; // WHERE子句
List* groupClause; // GROUP BY子句
Node* havingClause; // HAVING子句
List* windowClause; // WINDOW子句
WithClause* withClause; // WITH子句
List* valuesLists; // FROM子句中未转换的表达式,用来保存常量表
List* sortClause; // ORDER BY子句
Node* limitOffset; // OFFSET子句
Node* limitCount; // LIMIT子句
List* lockingClause; // FOR UPDATE子句
HintState* hintState;
SetOperation op; // 查询语句的集合操作
bool all; // 集合操作是否指定ALL关键字
struct SelectStmt* larg; // 左子节点
struct SelectStmt* rarg; // 右子节点
……
} SelectStmt;
这个结构体可以看作一个多叉树,每个叶子节点都表达了SELECT查询语句中的一个语法结构,对应到gram.y中,它会有一个SelectStmt。代码如下:
simple_select:
SELECT hint_string opt_distinct target_list
into_clause from_clause where_clause
group_clause having_clause window_clause
{
SelectStmt *n = makeNode(SelectStmt);
n->distinctClause = $3;
n->targetList = $4;
n->intoClause = $5;
n->fromClause = $6;
n->whereClause = $7;
n->groupClause = $8;
n->havingClause = $9;
n->windowClause = $10;
n->hintState = create_hintstate($2);
n->hasPlus = getOperatorPlusFlag();
$$ = (Node *)n;
}
……
simple_select除了上面的基本形式,还可以表示为其他形式,如VALUES子句、关系表达式、多个SELECT语句的集合操作等,这些形式会进一步的递归处理,最终转换为基本的simple_select形式。代码如下:
simple_select:
……
| values_clause { $$ = $1; }
| TABLE relation_expr
……
| select_clause UNION opt_all select_clause
{
$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
}
| select_clause INTERSECT opt_all select_clause
{
$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
}
| select_clause EXCEPT opt_all select_clause
{
$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
}
| select_clause MINUS_P opt_all select_clause
{
$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
}
;
从simple_select语法分析结构可以看出,一条简单的查询语句由以下子句组成:去除行重复的distinctClause、目标属性targetList、SELECT INTO子句intoClause、FROM子句fromClause、WHERE子句whereClause、GROUP BY子句groupClause、HAVING子句havingClause、窗口子句windowClause和plan_hint子句。在成功匹配simple_select语法结构后,将会创建一个Statement结构体,将各个子句进行相应的赋值。对simple_select而言,目标属性、FROM子句、WHERE子句是最重要的组成部分。
目标属性对应语法定义中的target_list,由若干个target_el组成。target_el可以定义为表达式、取别名的表达式和“*”等。代码如下:
target_list:
target_el { $$ = list_make1($1); }
| target_list ',' target_el { $$ = lappend($1, $3); }
;
target_el: a_expr AS ColLabel
……
| a_expr IDENT
……
| a_expr
……
| '*'
……
| c_expr VALUE_P
……
| c_expr NAME_P
……
| c_expr TYPE_P
……
;
当成功匹配到一个target_el后,会创建一个ResTarget结构体,用于存储目标对象的全部信息。ResTarget结构如下。
typedef struct ResTarget {
NodeTag type;
char *name; // AS指定的目标属性的名称,没有则为空
List *indirection; // 通过属性名、*号引用的目标属性,没有则为空
Node *val; // 指向各种表达式
int location; // 符号出现的位置
} ResTarget;
FROM子句对应语法定义中的from_clause,由FROM关键字和from_list组成,而from_list则由若干个table_ref组成。table_ref可以定义为关系表达式、取别名的关系表达式、函数、SELECT语句、表连接等形式。代码如下:
from_clause:
FROM from_list { $$ = $2; }
| /*EMPTY*/{ $$ = NIL; }
;
from_list:
table_ref { $$ = list_make1($1); }
| from_list ',' table_ref { $$ = lappend($1, $3); }
;
table_ref: relation_expr
……
| relation_expr alias_clause
……
| relation_expr opt_alias_clause tablesample_clause
……
| relation_expr PARTITION '(' name ')'
……
| relation_expr BUCKETS '(' bucket_list ')'
……
| relation_expr PARTITION_FOR '(' maxValueList ')'
……
| relation_expr PARTITION '(' name ')' alias_clause
……
| relation_expr PARTITION_FOR '(' maxValueList ')'
alias_clause
……
| func_table
……
| func_table alias_clause
……
| func_table AS '(' TableFuncElementList ')'
……
| func_table AS ColId '(' TableFuncElementList ')'
……
| func_table ColId '(' TableFuncElementList ')'
……
| select_with_parens
……
| select_with_parens alias_clause
……
| joined_table
……
| '(' joined_table ')' alias_clause
……
;
以FROM子句中的关系表达式为例,最终会定义为ColId的相关形式,表示为表名、列名等的定义。代码如下:
relation_expr:
qualified_name
……
| qualified_name '*'
……
| ONLY qualified_name
……
| ONLY '(' qualified_name ')'
……
;
qualified_name:
ColId
……
| ColId indirection
……
在捕获到ColId后,会创建一个RangeVar结构体,用来存储相关信息。RangeVar结构如下。
typedef struct RangeVar {
NodeTag type;
char* catalogname; // 表的数据库名
char* schemaname; // 表的模式名
char* relname; // 表或者序列名
char* partitionname; //记录分区表名
InhOption inhOpt; // 是否将表的操作递归到子表上
char relpersistence; / 表类型,普通表/unlogged表/临时表/全局临时表
Alias* alias; // 表的别名
int location; // 符号出现的位置
bool ispartition; // 是否为分区表
List* partitionKeyValuesList;
bool isbucket; // 当前是否为哈希桶类型的表
List* buckets; // 对应的哈希桶中的桶
int length;
#ifdef ENABLE_MOT
Oid foreignOid;
#endif
} RangeVar;
WHERE子句给出了元组的约束信息,对应语法定义中的where_clause,由WHERE关键字和一个表达式组成。例如:
where_clause:
WHERE a_expr { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
表达式可以为一个常量表达式或者属性,也可以为子表达式的运算关系。例如:
a_expr: c_expr { $$ = $1; }
| a_expr TYPECAST Typename
{ $$ = makeTypeCast($1, $3, @2); }
| a_expr COLLATE any_name
……
| a_expr AT TIME ZONE a_expr
……
| '+' a_expr
……
;
对于运算关系,会调用makeSimpleA_Expr函数生成A_Expr结构体,存储表达式的相关信息。A_Expr结构如下,字段lexpr和rexpr分别保存左、右两个子表达式的相关信息。代码如下:
typedef struct A_Expr {
NodeTag type;
A_Expr_Kind kind; // 表达式类型
List *name; // 操作符名称
Node *lexpr; // 左子表达式
Node *rexpr; // 右子表达式
int location; // 符号出现的位置
} A_Expr;
simple_select的其他子句,如distinctClause、groupClause、havingClause等,语法分析方式类似。而其他SQL命令,如CREATE、INSERT、UPDATE、DELETE等,处理方式与SELECT命令类似,这里不做一一说明。
对于任何复杂的SQL语句,都可以拆解为多个基本的SQL命令执行。在完成词法分析和语法分析后,raw_parser函数会将所有的语法分析树封装为一个List结构,名为raw_parse_tree_list,返回给exec_simple_query函数,用于后面的语义分析、查询重写等步骤,该List中的每个ListCell包含一个语法树。