(Elastic Advent) Day4:《將sql轉換爲es的DSL》

es如今幾乎已是開源搜索引擎的事實標準了,搭建簡易,使用方便。不過在不少公司裏(包括我司的部分部門),並非把它當搜索引擎來用,而是當db來用。由於自己查詢/搜索原理的區別,使es在千萬或者億級的數據中進行邏輯篩選相對高效。例如一些wms、工單查詢系統,單表幾十個甚至上百個字段,若是在數據庫裏爲每種類型的查詢都創建合適的索引,成本比較高,更不用說索引建多了還會影響到插入速度,後期的索引優化也是比較麻煩的問題。

不過若是把es當db來使的話,始終會有一個繞不過去的坎。就是es的DSL。讓全部業務開發去學習dsl的話也不是不能夠,但DSL真的有點反人類(不要打我)。簡單的a and b或者a or b還比較容易寫,若是我要的是a and (b and (c or d) and e)的查詢邏輯,那我以爲誰寫都會暈。即便是用官方或者第三方提供的client,若是需求多種多樣的話,想要靈活地實現`需求=>DSL`的過程仍是比較痛苦。

對於業務開發來講,固然是sql更平易近人(畢竟寫了這麼多年CRUD)。因此還有一種歪門邪道的流派,直接把sql轉成DSL。要作sql和DSL轉換的工做,須要進行sql的解析,先不要怵,這個年代找一個靠譜的sql parser仍是比較容易的。好比阿里開源的druid鏈接池裏的sql模塊:
 
https://github.com/alibaba/dru ... d/sql

由於筆者的實現是用的下面這個golang版的parser:

https://github.com/xwb1989/sqlparser

因此用這個來舉例吧~

這個是其做者從youtube/vitness裏提取並進行改進的一個parser,咱們能用到的是一部分子集功能,只須要解析select類的sql。

先舉個簡單的sql的例子:git

select * from x_order where userId = 1 order by id desc limit 10,1;

解析以後會變成golang的一個struct,來看看具體的定義:

&sqlparser.Select{
    Comments:sqlparser.Comments(nil),
    Distinct:"",
    SelectExprs:sqlparser.SelectExprs{(*sqlparser.StarExpr)(0xc42000aee0)},
    From:sqlparser.TableExprs{(*sqlparser.AliasedTableExpr)(0xc420016930)},
    Where:(*sqlparser.Where)(0xc42000afa0),
    GroupBy:sqlparser.GroupBy(nil),
    Having:(*sqlparser.Where)(nil),
    OrderBy:sqlparser.OrderBy{(*sqlparser.Order)(0xc42000af20)},
    Limit:(*sqlparser.Limit)(0xc42000af80),
    Lock:""
}



sql的select語句在被解析以後生成一個Select的結構體,若是咱們不關心使用者須要的字段的話,能夠先把SelectExprs/Distinct/Comments/Lock裏的內容忽略掉。若是不是分組統計類的需求,也能夠先把GroupBy/Having忽略掉。這裏咱們關心的就剩下From、Where、OrderBy和Limit。

From對應的TableExprs實際上能夠認爲是簡單的字符串,這裏的值其實就是`x_order`。

OrderBy其實是一個元素爲github

type Order struct {
    Expr      ValExpr
    Direction string
}\


的數組。

Limit也很簡單,golang

type Limit struct {
    Offset, Rowcount ValExpr
}


其實就是倆數字。

那麼剩下的就是這個Where結構了。where會被解析爲AST(`https://en.wikipedia.org/wiki/Abstract_syntax_tree`),中文是抽象語法樹。在不說子查詢之類的狀況下,這個AST也不會太複雜,畢竟where後面的狀況比起編譯原理裏的程序語言來講狀況仍是要少得多的。以上述的sql爲例,這裏解析出來的Where結構是這樣的:sql

&sqlparser.Where{
    Type:"where",
    Expr:(*sqlparser.ComparisonExpr)(0xc420016a50)
}



只有一個節點,一個ComparisonExpr表達式,這個ComparisonExpr,中文比較表達式,指代的就是咱們sql裏的`user_id = 1`。實際上咱們能夠認爲這個"比較表達式"便是全部複雜AST的葉子節點。葉子結點在AST遍歷的時候通常也就是遞歸的終點。由於這裏的查詢比較簡單,整棵AST只有一個節點,即根節點和葉子節點都是這個ComparisonExpr。

再來一個複雜點的例子。數據庫

select * from users where user_id = 1 and product_id =2

=>

&sqlparser.Where{
    Type:"where",
    Expr:(*sqlparser.AndExpr)(0xc42000b020)
}

AndExpr有Left和Right兩個成員,分別是:

Left:
&sqlparser.ComparisonExpr{
    Operator:"=",
    Left:(*sqlparser.ColName)(0xc4200709c0),
    Right:sqlparser.NumVal{0x31}
}

Right:
&sqlparser.ComparisonExpr{
    Operator:"=",
    Left:(*sqlparser.ColName)(0xc420070a50),
    Right:sqlparser.NumVal{0x32}
}



稍微有一些二叉樹的樣子了吧。把這棵簡單的樹畫出來:


數組



回到文章開頭的那個複雜的例子:微信

a and (b and (c or d) and e)

=>

select * from user_history where user_id = 1 and (product_id = 2 and (star_num = 4 or star_num = 5) and banned = 1)



看着真夠麻煩的,咱們把這棵樹畫出來:


學習



這樣看着就直觀多了。咱們有了AST的結構,那要怎麼對應到es的查詢DSL呢?少安毋躁。

咱們知道es的bool query是能夠進行嵌套的,因此實際上咱們能夠一樣能夠構造出樹形結構的bool query。這裏把bool嵌套must和bool嵌套should簡化一下,寫成boolmust和boolshould:

例如a and (b and c)優化

query {
    boolmust {
        a,
        boolmust {
            b,
            c
        }
    }
}



咱們把query內部的第一個boolmust看成根節點,內部嵌套的a和另外一個boolmust看成它的兩個子節點,而後b和c又是這個boolmust的子節點。能夠看出來,實際上這棵樹和AST的節點能夠一一對應。

再回到文章開頭的例子,a and (b and (c or d) and e):ui

query {
    boolmust {
        a,
        boolmust {
            b,
            boolshould {
                c,
                d
            },
            e
        }
    }
}


和前文中ast來作個簡單的結構對比~




和前文中sql的where解析後的AST樹也是徹底匹配的。思路來了,只要對sql解析生成的AST進行遞歸,便可獲得這棵樹。固然了,這裏還能夠進行一些優化,若是子節點的類型和父
節點的類型一致,例如都是and表達式或者都是or表達式,咱們能夠在生成dsl的時候將其做爲並列的節點進行合併,這裏再也不贅述。


在遞歸中有這麼幾種狀況:

AndExpr => bool must [{left}, {right}]
OrExpr => bool should [{left}, {right}]
ComparisonExpr => 通常是葉子節點
ParenBoolExpr => 指代括號表達式,其實內部是上述三種節點的某一種,因此直接取出內部節點按上述方法來處理



這樣問題就變成了如何處理AST的葉子節點。前面提到了葉子節點實際上就是Comparison Expression。只要簡單進行一些對應便可,下面是咱們的項目裏的一些對應關係,僅供參考:




最後再附上demo
 
https://github.com/cch123/elasticsql


本文分享自微信公衆號 - Elastic中文社區(elastic-cn)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索