MLSQL智能代碼提示

MLSQL智能補全功能現(xiàn)階段是作為MLSQL的一個插件的形式提供的吸占。在發(fā)布第一個版本后,我們會將其獨立出來,作為一個通用的SQL提示引擎來進(jìn)行后續(xù)的發(fā)展。為了方便對該項目指代将硝,我們后續(xù)使用 【MLSQL Code Intelligence】

項目地址: mlsql-autosuggest

當(dāng)前狀態(tài)

【積極開發(fā)中,還未發(fā)布穩(wěn)定版本】

目標(biāo)

【MLSQL Code Intelligence】目標(biāo)分成兩個屏镊,第一個是標(biāo)準(zhǔn)SQL補全:

  1. SQL關(guān)鍵字補全
  2. 表/字段屬性/函數(shù)補全
  3. 可二次開發(fā)自定義對接任何Schema Provider

第二個是MLSQL語法補全:

  1. 支持各種數(shù)據(jù)源提示
  2. 支持臨時表提示
  3. 支持各種ET組件參數(shù)提示以及名稱提示

對于表和字段補依疼,函數(shù)補全,相比其他一些SQL代碼提示工具闸衫,該插件可根據(jù)當(dāng)前已有的信息精確推斷涛贯。比如:

select  no_result_type, keywords, search_num, rank
from(
  select  [CURSOR is HERE] row_number() over (PARTITION BY no_result_type order by search_num desc) as rank
  from(
    select jack1.*,no_result_type, keywords, sum(search_num) AS search_num
    from jack.drugs_bad_case_di as jack1,jack.abc jack2
    where hp_stat_date >= date_sub(current_date,30)
    and action_dt >= date_sub(current_date,30)
    and action_type = 'search'
    and length(keywords) > 1
    and (split(av, '\\.')[0] >= 11 OR (split(av, '\\.')[0] = 10 AND split(av, '\\.')[1] = 9))
    --and no_result_type = 'indication'
    group by no_result_type, keywords
  )a
)b
where rank <=

鼠標(biāo)在第三行第十列,此時系統(tǒng)會自動提示:

  1. a [表名]
  2. jack1展開的所有列
  3. no_result_type
  4. keywords
  5. search_num

如果有接口提供schema信息蔚出,會自動展開*弟翘,并且獲取相關(guān)層級的信息從而非常精準(zhǔn)的進(jìn)行提示。同時骄酗,如果有shcema信息稀余,對每個字段也支持類型提示。插件提供了非常友好和簡單的接口方便用戶接入自己的元數(shù)據(jù)趋翻。

用戶指南

部署

參考部署文檔 MLSQL部署
該插件作為MLSQ默認(rèn)插件睛琳,所以開箱即用

接口使用

訪問接口: http://127.0.0.1:9003/run/script?executeMode=autoSuggest

參數(shù)1: sql SQL腳本
參數(shù)2: lineNum 光標(biāo)所在的行號 從1開始計數(shù)
參數(shù)3: columnNum 光標(biāo)所在的列號,從1開始計數(shù)

比如我用Scala寫一個client:

object Test {
  def main(args: Array[String]): Unit = {
    val time = System.currentTimeMillis()
    val response = Request.Post("http://127.0.0.1:9003/run/script").bodyForm(
      Form.form().add("executeMode", "autoSuggest").add("sql",
        """
          |select spl  from jack.drugs_bad_case_di as a
          |""".stripMargin).add("lineNum", "2").add("columnNum", "10").build()
    ).execute().returnContent().asString()
    println(System.currentTimeMillis() - time)
    println(response)
  }

}

最后結(jié)果如下:

[{"name":"split",
"metaTable":{"key":{"db":"__FUNC__","table":"split"},
"columns":[
{"name":null,"dataType":"array","isNull":true,"extra":{"zhDoc":"\nsplit函數(shù)踏烙。用于切割字符串师骗,返回字符串?dāng)?shù)組\n"}},{"name":"str","dataType":"string","isNull":false,"extra":{"zhDoc":"待切割字符"}},
{"name":"pattern","dataType":"string","isNull":false,"extra":{"zhDoc":"分隔符"}}]},
"extra":{}}]

可以知道提示了split,并且這是一個函數(shù),函數(shù)的參數(shù)以及返回值都有定義讨惩。

編程開發(fā)

首先初始化兩個此法分析器:

object AutoSuggestController {
  val lexerAndParserfactory = new ReflectionLexerAndParserFactory(classOf[DSLSQLLexer], classOf[DSLSQLParser]);
  val mlsqlLexer = new LexerWrapper(lexerAndParserfactory, new DefaultToCharStream)

  val lexerAndParserfactory2 = new ReflectionLexerAndParserFactory(classOf[SqlBaseLexer], classOf[SqlBaseParser]);
  val sqlLexer = new LexerWrapper(lexerAndParserfactory2, new RawSQLToCharStream)

}

接著創(chuàng)建AutoSuggestContext,然后用此法分析器解析sql,最后傳遞給context,同時傳遞行號和列好辟癌,即可。


val sql = params("sql")
val lineNum = params("lineNum").toInt
val columnNum = params("columnNum").toInt

val context = new AutoSuggestContext(ScriptSQLExec.context().execListener.sparkSession,
  AutoSuggestController.mlsqlLexer,
  AutoSuggestController.sqlLexer)

val sqlTokens = context.lexer.tokenizeNonDefaultChannel(sql).tokens.asScala.toList

val tokenPos = LexerUtils.toTokenPos(sqlTokens, lineNum, columnNum)
JSONTool.toJsonStr(context.build(sqlTokens).suggest(tokenPos))

開發(fā)者指南

解析流程

【MLSQL Code Intelligence】復(fù)用了MLSQL/Spark SQL的lexer荐捻,重寫了parser部分黍少。因為代碼提示有其自身特點,就是句法在書寫過程中处面,大部分情況下都是錯誤的厂置,無法使用嚴(yán)格的parser來進(jìn)行解析。

使用兩個Lexer的原因是因為魂角,MLSQL Lexer主要用來解析整個MLSQL腳本昵济,Spark SQL Lexer主要用來解決標(biāo)準(zhǔn)SQL中的select語句。但是因為該項目高度可擴展野揪,用戶也可以自行擴展到其他標(biāo)準(zhǔn)SQL的語句中访忿。

以select語句里的代碼提示為例,整個解析流程為:

  1. 使用MLSQL Lexer 將腳本切分成多個statement
  2. 每個statement 會使用不同的Suggester進(jìn)行下一步解析
  3. 使用SelectSuggester 對select statement進(jìn)行解析
  4. 首先對select語句構(gòu)建一個非常粗粒度的AST,節(jié)點為每個子查詢囱挑,同時構(gòu)建一個表結(jié)構(gòu)層級緩存信息TABLE_INFO
  5. 將光標(biāo)位置轉(zhuǎn)化為全局TokenPos
  6. 將全局TokenPos轉(zhuǎn)化select語句相對TokenPos
  7. 根據(jù)TokenPos遍歷Select AST樹醉顽,定位到簡單子語句
  8. 使用project/where/groupby/on/having子suggester進(jìn)行匹配,匹配的suggester最后完成提示邏輯

在AST樹種平挑,每個子語句都可以是不完整的游添。由上面流程可知,我們會以statement為粗粒度工作context,然后對于復(fù)雜的select語句通熄,最后我們會進(jìn)一步細(xì)化到每個子查詢?yōu)楣ぷ鱟ontext唆涝。這樣為我們編碼帶來了非常大的便利。

TokenMatcher工具類

在【MLSQL Code Intelligence】中唇辨,最主要的工作是做token匹配廊酣。我們提供了TokenMatcher來完成token的匹配。TokenMatcher支持前向和后向匹配赏枚。如下token序列:

select a , b , c from jack

假設(shè)我想以token index 3(b) 為起始點亡驰,前向匹配一個逗號晓猛,identify 可以使用如下語法:

val tokenMatcher = TokenMatcher(tokens,4).forward.eat(Food(None, TokenTypeWrapper.DOT), Food(None, SqlBaseLexer.IDENTIFIER)).build

接著你可以調(diào)用 tokenMatcher.isSuccess來判斷是否匹配成功,可以調(diào)用tokenMatcher.get 獲取匹配得到匹配成功后的index,通過tokenMatcher.getMatchTokens 獲取匹配成功的token集合凡辱。

注意戒职,TokenMatcher起始位置是包含的,也就是他會將起始位置的token也加入到匹配token里去透乾。所以在上面的例子中洪燥,start 是4而不是3. 更多例子可以查看源碼。

快速參與貢獻(xiàn)該項目

【MLSQL Code Intelligence】 需要大量函數(shù)的定義乳乌,方便在用戶使用時給予提示捧韵。下面是我實現(xiàn)的split 函數(shù)的代碼:

class Splitter extends FuncReg {

  override def register = {
    val func = MLSQLSQLFunction.apply("split").
      funcParam.
      param("str", DataType.STRING, false, Map("zhDoc" -> "待切割字符")).
      param("pattern", DataType.STRING, false, Map("zhDoc" -> "分隔符")).
      func.
      returnParam(DataType.ARRAY, true, Map(
        "zhDoc" ->
          """
            |split函數(shù)。用于切割字符串汉操,返回字符串?dāng)?shù)組
            |""".stripMargin
      )).
      build
    func
  }

}

用戶只要用FunctionBuilder去構(gòu)建函數(shù)簽名即可再来。這樣用戶在使用該函數(shù)的時候就能得到非常詳盡的使用說明和參數(shù)說明。同時客情,我們也可以通過該函數(shù)簽名獲取嵌套函數(shù)處理后的字段的類型信息其弊。

用戶只要按上面的方式添加更多函數(shù)到tech.mlsql.autosuggest.funcs包下即可。系統(tǒng)會自動掃描該包里的實現(xiàn)并且注冊膀斋。

子查詢層級結(jié)構(gòu)

對于語句:

select  no_result_type, keywords, search_num, rank
from(
  select  keywords, search_num, row_number() over (PARTITION BY no_result_type order by search_num desc) as rank
  from(
    select *,no_result_type, keywords, sum(search_num) AS search_num
    from jack.drugs_bad_case_di,jack.abc jack
    where hp_stat_date >= date_sub(current_date,30)
    and action_dt >= date_sub(current_date,30)
    and action_type = 'search'
    and length(keywords) > 1
    and (split(av, '\\.')[0] >= 11 OR (split(av, '\\.')[0] = 10 AND split(av, '\\.')[1] = 9))
    --and no_result_type = 'indication'
    group by no_result_type, keywords
  )a
)b
where rank <=

形成的AST結(jié)構(gòu)樹如下:

select no_result_type , keywords , search_num , rank from ( select keywords , search_num , row_number ( ) over
 ( PARTITION BY no_result_type order by search_num desc ) as rank from ( select * , no_result_type , keywords ,
 sum ( search_num ) AS search_num from jack . drugs_bad_case_di , jack . abc jack where hp_stat_date >= date_sub (
 current_date , 30 ) and action_dt >= date_sub ( current_date , 30 ) and action_type = 'search' and length (
 keywords ) > 1 and ( split ( av , '\\.' ) [ 0 ] >= 11 OR ( split
 ( av , '\\.' ) [ 0 ] = 10 AND split ( av , '\\.' ) [ 1 ]
 = 9 ) ) group by no_result_type , keywords ) a ) b where rank <=


=>select keywords , search_num , row_number ( ) over ( PARTITION BY no_result_type order by search_num desc ) as
 rank from ( select * , no_result_type , keywords , sum ( search_num ) AS search_num from jack . drugs_bad_case_di
 , jack . abc jack where hp_stat_date >= date_sub ( current_date , 30 ) and action_dt >= date_sub ( current_date
 , 30 ) and action_type = 'search' and length ( keywords ) > 1 and ( split ( av ,
 '\\.' ) [ 0 ] >= 11 OR ( split ( av , '\\.' ) [ 0 ] = 10
 AND split ( av , '\\.' ) [ 1 ] = 9 ) ) group by no_result_type , keywords )
 a ) b


==>select * , no_result_type , keywords , sum ( search_num ) AS search_num from jack . drugs_bad_case_di , jack
 . abc jack where hp_stat_date >= date_sub ( current_date , 30 ) and action_dt >= date_sub ( current_date , 30
 ) and action_type = 'search' and length ( keywords ) > 1 and ( split ( av , '\\.' )
 [ 0 ] >= 11 OR ( split ( av , '\\.' ) [ 0 ] = 10 AND split
 ( av , '\\.' ) [ 1 ] = 9 ) ) group by no_result_type , keywords ) a

我們可以看到一共嵌套了兩層梭伐,每層都有一個子查詢。

對此形成的TABLE_INFO結(jié)構(gòu)如下:

2:
List(
MetaTableKeyWrapper(MetaTableKey(None,Some(jack),drugs_bad_case_di),None), 
MetaTableKeyWrapper(MetaTableKey(None,None,null),Some(a)), 
MetaTableKeyWrapper(MetaTableKey(None,Some(jack),abc),Some(jack)))
1:
List(MetaTableKeyWrapper(MetaTableKey(None,None,null),Some(b)))
0:
List()

0層級為最外層語句仰担;1層級為第一個子查詢糊识;2層級為第二個子查詢,他包含了子查詢別名以及該子查詢里所有的實體表信息摔蓝。

上面只是為了顯示赂苗,實際上還包含了所有列的信息。這意味著贮尉,如果我要補全0層記得 project,那我只需要獲取1層級的信息拌滋,可以補全b表名稱或者b表對應(yīng)的字段。同理類推猜谚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末败砂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子魏铅,更是在濱河造成了極大的恐慌昌犹,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件览芳,死亡現(xiàn)場離奇詭異斜姥,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門铸敏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缚忧,“玉大人,你說我怎么就攤上這事搞坝∩η矗” “怎么了魁袜?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵桩撮,是天一觀的道長。 經(jīng)常有香客問我峰弹,道長店量,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任鞠呈,我火速辦了婚禮融师,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚁吝。我一直安慰自己旱爆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布窘茁。 她就那樣靜靜地躺著怀伦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪山林。 梳的紋絲不亂的頭發(fā)上房待,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音驼抹,去河邊找鬼桑孩。 笑死,一個胖子當(dāng)著我的面吹牛框冀,可吹牛的內(nèi)容都是我干的流椒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼明也,長吁一口氣:“原來是場噩夢啊……” “哼宣虾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诡右,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤安岂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帆吻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體域那,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了次员。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片败许。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖淑蔚,靈堂內(nèi)的尸體忽然破棺而出市殷,到底是詐尸還是另有隱情,我是刑警寧澤刹衫,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布醋寝,位于F島的核電站,受9級特大地震影響带迟,放射性物質(zhì)發(fā)生泄漏音羞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一仓犬、第九天 我趴在偏房一處隱蔽的房頂上張望嗅绰。 院中可真熱鬧,春花似錦搀继、人聲如沸窘面。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽财边。三九已至,卻和暖如春险毁,著一層夾襖步出監(jiān)牢的瞬間制圈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工畔况, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲸鹦,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓跷跪,卻偏偏與公主長得像馋嗜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吵瞻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354