學習RadonDB源碼(八)

語法解析

我們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應該是這樣的:

這麼看來根節點的右子樹會變得非常非常大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章