語法解析
我們DBA平時通過SQL和數據庫進行交互。SQL是一門典型的第四代語言,只用說明我要什麼,而不需要寫出來我要怎麼做,這一點在之前的文章中講過。
既然有查詢優化器,那麼一定要首先有一個翻譯的地方存在,將SQL翻譯成一種優化器能懂的語言。
這就是AST,所謂抽象語法樹。
我認爲,AST算是數據庫王冠上一顆閃亮的寶石了。
下面透過RadonDB的源碼,追尋一下AST的蹤跡吧。
// Insert represents an INSERT or REPLACE statement.
// Per the MySQL docs, http://dev.mysql.com/doc/refman/5.7/en/replace.html
// Replace is the counterpart to `INSERT IGNORE`, and works exactly like a
// normal INSERT except if the row exists. In that case it first deletes
// the row and re-inserts with new values. For that reason we keep it as an Insert struct.
// Replaces are currently disallowed in sharded schemas because
// of the implications the deletion part may have on vindexes.
type Insert struct {
Action string
Comments Comments
Ignore string
Table TableName
Columns Columns
Rows InsertRows
OnDup OnDup
}
const (
// InsertStr represents insert action.
InsertStr = "insert"
// ReplaceStr represents replace action.
ReplaceStr = "replace"
)
// Format formats the node.
func (node *Insert) Format(buf *TrackedBuffer) {
buf.Myprintf("%s %v%sinto %v%v %v%v",
node.Action,
node.Comments, node.Ignore,
node.Table, node.Columns, node.Rows, node.OnDup)
}
// WalkSubtree walks the nodes of the subtree.
func (node *Insert) WalkSubtree(visit Visit) error {
if node == nil {
return nil
}
return Walk(
visit,
node.Comments,
node.Table,
node.Columns,
node.Rows,
node.OnDup,
)
}
// InsertRows represents the rows for an INSERT statement.
type InsertRows interface {
iInsertRows()
SQLNode
}
以上是比較簡單的Insert的AST代碼實現部分,從代碼裏看到,這個AST包括了insert和replace語法的解析,這裏就定義了兩個常量。
接下來的Format方法開始對Insert語句進行格式化,這裏就可以觀察出Insert結構體的所有域是幹什麼的了。但是這裏我沒有看到values,我覺得有點奇怪,這一點等我以後看看別的ast實現再琢磨琢磨。
WalkSubtree方法負責去構造樹,採用遞歸的辦法:
func Walk(visit Visit, nodes ...SQLNode) error {
for _, node := range nodes {
if node == nil {
continue
}
kontinue, err := visit(node)
if err != nil {
return err
}
if kontinue {
err = node.WalkSubtree(visit) //有點遞歸的意思了
if err != nil {
return err
}
}
}
return nil
}
這裏用了遞歸的辦法,我也不知道效率怎麼樣,但是我是不喜歡遞歸的,遞歸讓程序變得令人迷惑。
下面看看比較複雜的select部分,也是SQL優化的重點部分。
考慮一下這個SQL:
select * from test where id = 1;
如果這樣的語句構造抽象語法樹,是一個什麼樣子呢?
說起來有點丈二和尚摸不着頭腦了,那麼先看看AST中的代碼是怎麼寫And條件的:
func (node *Select) AddWhere(expr Expr) {
if _, ok := expr.(*OrExpr); ok {
expr = &ParenExpr{Expr: expr}
}
if node.Where == nil {
node.Where = &Where{
Type: WhereStr,
Expr: expr,
}
return
}
node.Where.Expr = &AndExpr{
Left: node.Where.Expr,
Right: expr,
}
}
上面那個SQL的解析到這裏就會變成一個結構體:
type Where struct {
Type string
Expr Expr
}
具體說來是這樣的:
{
Type:"Where"
Expr://一個具體的地址
}
現在來構造一下這顆樹:
不要笑,事實就是這樣,只有一個where條件代表只能構造一個單節點的樹。那麼接下來把SQL寫的複雜一點:
select * from test where id = 1 and name = 'quan';
這次會生成一個如何的結構體呢:
node.Where.Expr = &AndExpr{
Left: node.Where.Expr,
Right: expr,
}
我們姑且將所有的條件都加一個序號,從左向右遞增,那麼id是1,name是2,這個時候我們可以根據這段代碼構造一個樹:
嗯,有模有樣的一棵二叉樹了,再把條件整的複雜一點:
select * from test where id = 1 and name = 'quan' and score > 60;
繼續我們的編號準則,score的編號是3,此時的AST應該是這樣的:
這麼看來根節點的右子樹會變得非常非常大。