c# 怎樣能寫個sql的解析器

tansar 發表於 2022-06-30
C#

c# 怎樣能寫個sql的解析器

本示例主要是講明sql解析的原理,真實的原始碼下檢視 sql解析器原始碼
詳細示例DEMO 請檢視demo程式碼

前言

閱讀本文需要有一定正規表示式基礎 正規表示式基礎教程 ,和編譯原理的基礎。有使用過VUE的夥伴可能知道vue是自定了模版解析編譯器的,vue用的是標準的AST語法樹統計,如果對語法樹不了瞭解的請檢視 什麼是AST抽像語法樹

本示例介紹的是參考編譯原理 詞法分析->語法分析->構建AST語法樹->解析成目標sql 的流程來實現

示例

sqlserver 的一條查詢語句

select  a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a

假如我們要將以上程式碼進行格式化成以下方式

select  [a].[UniqueCode],[a].[BarCode],[a].[CategoryId] from [GD_UniqueCodeInfo] as [a]

分析

首先我們來分析一下這個語句有什麼特點。

  1. 找關鍵詞
    這個sql語法有三個關鍵詞如select ,from,as

  2. 找結構
    有欄位資訊a.UniqueCode,a.BarCode,a.CategoryId,有表名資訊GD_UniqueCodeInfo 還有 被重新命名的表資訊a 這些資訊可能符合命名規範可能用些不符合,那麼在解析時都要進行檢測出來

  3. 識別符號
    在生成的目標sql語句中有[] 這個的作用主要是萬一欄位名出現與關鍵詞有相同的欄位名稱能進行正常識別

開始

首先我們先建立兩個c#解析正規表示式的方法

這個方法就是可以將正規表示式中的匹配資料提出來返回一個字典資料

  public static Dictionary<string, string> RegexGrp(string regex,string text)
  {
        Regex _regex = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Multiline);
        Dictionary<string, string> _dic = new Dictionary<string, string>();
        Match _match = _regex.Match(text);
        while (_match.Success)
        {
            foreach (string name in _regex.GetGroupNames())
            {
                if(!_dic.ContainsKey(name))
                    _dic.Add(name, _match.Groups[_regex.GroupNumberFromName(name)].Value);
            }
            _match = _match.NextMatch();
        }
        return _dic;
  }

檢測正則表達工是否正確匹配

public static bool RegexMatch(string regex, string text)
        {
            Regex _regex = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            Match _match = _regex.Match(text);
            return _match.Success;
        }

第一步 先檢測這個sql語句是否是一個查詢語句

正則程式碼:^\s*(?<cmd>select)\s+(?<field>[\w\s\S]+(?=\bfrom\b))(?:\bfrom\b)(?<from>(?:[\s]+)(?<flag>[\#]{1,2}|[\@]{1})?(?<tab>[\w]+)\s*[\s\w\S]*)

那麼我們來驗證下
通過把要解析的SQL語句放入測試工具中執行
c# 怎樣能寫個sql的解析器

在右下方的區域通過正則匹配已經把該語句結構已經拆解出來了
cmd:select
field:a.UniqueCode,a.BarCode,a.CategoryId
tab:GD_UniqueCodeInfo

一下就把SQL語句結構化出來了,有匹配結果說明是一個正常的sql語句

第二步 通過程式碼獲取結構資訊

  string sql="select  a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a";
  Dictionary<string, string> dic =RegexGrp(@"^\s*(?<cmd>select)\s+(?<field>[\w\s\S]+(?=\bfrom\b))(?:\bfrom\b)(?<from>(?:[\s]+)(?<flag>[\#]{1,2}|[\@]{1})?(?<tab>[\w]+)\s*[\s\w\S]*)",sql);

  if(dic.ConstainsKey("cmd"))
  {
    // 說明匹配成功
    Console.Write(dic["cmd"]);
    
  }


拆解select 後要把select 替換為空剩餘的sql 為 a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a

第三步 拆解欄位

正規表示式:^\s*(?<field>[\w\s\S]*?(?=\bfrom\b))
兩通過測試工具測試一下
c# 怎樣能寫個sql的解析器

那麼可以通過程式碼獲取出來

  string sql="a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a";
  Dictionary<string, string> dic =RegexGrp(@"^\s*(?<field>[\w\s\S]*?(?=\bfrom\b))",sql);
  if (dic.ContainsKey("field"))
  {
    //說明匹配成功 
  }

欄位是有多個的 還要單獨拆解成一個一個的欄位,拆解欄位的這個就不詳細描述了,可以繼續用正規表示式也可以用Split(',') 進行分拆

var _field=dic["field"];
var fields=_field.Split(',')

拆解完欄位後 剩餘的sql:from GD_UniqueCodeInfo as a

拆解from

正規表示式:^\s*(?:\bfrom\b)(?<from>(?:[\s]+)(?<table>(?:[\s]*)(?<flag>[\#]{1,2}|[\@]{1})?(?<tab>[\w]+))\s*(?:\bas\b\s*(?<asname>[\w]+))?\s*)
通過該正規表示式可以拆解出 通過 as 重新命名的表
下面通過正規表示式工具測試一下
c# 怎樣能寫個sql的解析器

那麼通過以下程式碼來獲取

string sql="from GD_UniqueCodeInfo as a";
  Dictionary<string, string> dic =RegexGrp(@"^\s*(?:\bfrom\b)(?<from>(?:[\s]+)(?<table>(?:[\s]*)(?<flag>[\#]{1,2}|[\@]{1})?(?<tab>[\w]+))\s*(?:\bas\b\s*(?<asname>[\w]+))?\s*)",sql);
  if (dic.ContainsKey("tab"))
  {
    //說明匹配成功 
  }

此時 就通過正規表示式拆解完成,但還需要對它進行結構化

以下是程式碼截圖片段
c# 怎樣能寫個sql的解析器

c# 怎樣能寫個sql的解析器

c# 怎樣能寫個sql的解析器

結果
c# 怎樣能寫個sql的解析器

請檢視demo程式碼

語法參考 hisql語法