Java 實現對Sql語句解析

最近要實現一個簡易的數據庫系統,除了要考慮如何高效的存儲和訪問數據,建立表關係外,對基本的sql查詢語句要做一個解析,這樣我們才能知道用戶的查詢要求;因爲時間關係,參考了已有的一篇文章,並對其實現中出的小問題給予更正,在這裏跟大家共享一下。原文請查閱http://www.cnblogs.com/pelephone/articles/sql-parse-single-word.html

第一步:先對sql語句進行預處理;

對於用戶,我們應該接受各種形式的查詢語句書寫,單行或者多行,語句中單個空格或者多個空格的間隔等等。但是我們要解析sql語句,就首先要讓對它們做標準化,這樣才能進行我們下一步處理。系統中的處理要求:

1)消除SQL語句前後的空白,將其中的連續空白字符(包括空格,TAB和回車換行)替換成單個空格;

2)將sql語句全變成小寫形式(或大寫形式);

3)在SQL語句的尾後加上結束符號“ENDOFSQL”(原因後面解釋)

例如:用戶輸入:“select c1,c2,c3 from  t1,t2, t3 where condi1=5 and condi6=6 or condi7=7 order 

by g1,g2

通過預處理應該爲:“select c1,c2,c3 from t1,t2,t3 where condi1=5 and condi6=6 or condi7=7 order by g1,g2”

第二步:將查詢語句切分爲語句塊;

以查詢語句爲例(本文中主要是以查詢語句作爲例子講解,其它刪除,插入等語句原理於此類似,因爲查詢語句相對複雜,所以用來i講解),正如上面我們標準化後的語句一樣,我們要進行下一步的,對錶中數據的處理,首先要知道是對那些表處理,要滿足那些條件,輸出那些屬性,以什麼順序輸出等。所以查詢語句就可以分割爲以下幾個塊:

1)select c1,c2,c3 from:屬性輸出塊;塊頭(start)select,塊尾(end)from,這個塊我們關心的信息(body):c1,c2,c3;以下塊類似分析

2)from....where; 涉及數據表塊。

3)where.....order by; 查詢條件塊。

4)order by.....ENDOFSQL; 屬性輸出順序。這裏也就看出了我們爲什麼要在查詢語句末尾加上結束符,是爲了最後一個塊的限定需要。

知道了如何分塊,接下來要做的就是在我們已經標準化的sql語句上進行塊的切割,這裏我們用到了正則表達式,以第二個塊from....where的查詢爲例,我們做個分析

"(from)(.+)( where | on | having | group by | order by | ENDOFSQL)“

以上就是第二個塊的正則匹配式(其它塊的匹配式下面也會給出),可以看出,在一個sql查詢語句中,from塊中跟from搭配出現的不只是where,還可以是on,having,group by等,那麼通過這個正則式我們可以得到如下的塊:

      from .... where

  from .... on

  from .... having

  from .... group by

  from .... order by

  from .... ENDOFSQL

這裏我們要注意一點,就是在通過正則式對sql語句進行匹配時,我們不能對整個sql語句進行一次匹配操作,因爲正則匹配是貪心匹配,它總是儘可能的向後查找,匹配到最大的語句段。就拿上述語句爲例,如果通過對整個sql語句進行一次匹配,得到的就不是from....where這個語句段而是from .... where .... order by。顯然這不是我們想要的。所以我們只能犧牲效率,通過對整個sql語句進行逐次遞增的查詢方式來查找相應的語句塊。給出一個查詢過程,加強理解,以上述sql語句爲例,對第一個語句塊的查找過程是

s
se
sel
sele
selec
select
select
select c
select c1
select c1,
select c1,c
select c1,c2
select c1,c2,
select c1,c2,c
select c1,c2,c3
select c1,c2,c3
select c1,c2,c3 f
select c1,c2,c3 fr
select c1,c2,c3 fro
select c1,c2,c3 from

這樣就找到第一個塊,以此類推,找第二個塊時候又從頭逐次遞增查找。

第三步:找到了各個塊,我們還要把我們最關心的信息提取出來,就是夾在語句塊頭和尾之間的body部分,這個就好實現了,一般的sql語句中都會用逗號來做分割,我們提取出各個塊的body信息。

步驟介紹完了,下面就上代碼,享樂吧...少年!

package com.sitinspring.common.sqlparser.single;

import java.util.List;

/** *//**
* 單句Sql解析器製造工廠
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class SqlParserUtil{
    /** *//**
     * 方法的主要入口
     * @param sql:要解析的sql語句
     * @return 返回解析結果
     */
    public String getParsedSql(String sql){
        sql=sql.trim();
        sql=sql.toLowerCase();
        sql=sql.replaceAll("\\s{1,}", " ");
        sql=""+sql+" ENDOFSQL";
        //System.out.println(sql);
        return SingleSqlParserFactory.generateParser(sql).getParsedSql();
    }
    
    /** *//**
     * SQL語句解析的接口
     * @param sql:要解析的sql語句
     * @return 返回解析結果
     */
    public List<SqlSegment> getParsedSqlList(String sql)
    {
        sql=sql.trim();
        sql=sql.toLowerCase();
        sql=sql.replaceAll("\\s{1,}", " ");
        sql=""+sql+" ENDOFSQL";
        //System.out.println(sql);
        return SingleSqlParserFactory.generateParser(sql).RetrunSqlSegments();
    }
    }
package com.sitinspring.common.sqlparser.single;

//import com.sitinspring.common.sqlparser.single.NoSqlParserException;
import java.util.ArrayList;
import java.util.List;
import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
* 單句Sql解析器,單句即非嵌套的意思
* @author 趙朝峯()
*
* @since 2013-6-10
* @version 1.00
*/
public abstract class BaseSingleSqlParser{
/** *//**
 * 原始Sql語句
 */
protected String originalSql;
/** *//**
 * Sql語句片段
 */
protected List<SqlSegment> segments;
/** *//**
 * 構造函數,傳入原始Sql語句,進行劈分。
 * @param originalSql
 */
public BaseSingleSqlParser(String originalSql){
    this.originalSql=originalSql;
    segments=new ArrayList<SqlSegment>();
    initializeSegments();
    splitSql2Segment();
}
/** *//**
 * 初始化segments,強制子類實現
 *
 */
protected abstract void initializeSegments();
/** *//**
 * 將originalSql劈分成一個個片段
 *
 */
protected void splitSql2Segment() {
    for(SqlSegment sqlSegment:segments)
    {
        sqlSegment.parse(originalSql);
    }
}
/** *//**
 * 得到解析完畢的Sql語句
 * @return
 */
public String getParsedSql() {
    
    //測試輸出各個片段的信息
    /*
    for(SqlSegment sqlSegment:segments)
    {
        String start=sqlSegment.getStart();
        String end=sqlSegment.getEnd();
        System.out.println(start);
        System.out.println(end);
    }
    */
    
    StringBuffer sb=new StringBuffer();
    for(SqlSegment sqlSegment:segments)
    {
        sb.append(sqlSegment.getParsedSqlSegment());
    }
    String retval=sb.toString().replaceAll("@+", "\n");
    return retval;
}
/** *//**
* 得到解析的Sql片段
* @return
*/
public List<SqlSegment> RetrunSqlSegments()
{
    int SegmentLength=this.segments.size();
    if(SegmentLength!=0)
    {
      List<SqlSegment> result=this.segments;
      return result;
    }
    else
    {
        //throw new Exception();
        return null;
    }
}
}
package com.sitinspring.common.sqlparser.single;

import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
*
* 單句刪除語句解析器
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class DeleteSqlParser extends BaseSingleSqlParser{
public DeleteSqlParser(String originalSql) {
    super(originalSql);
}
@Override
protected void initializeSegments() {
    segments.add(new SqlSegment("(delete from)(.+)( where | ENDOFSQL)","[,]"));
    segments.add(new SqlSegment("(where)(.+)( ENDOFSQL)","(and|or)"));
}
}
package com.sitinspring.common.sqlparser.single;

import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
*
* 單句查詢插入語句解析器
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class InsertSelectSqlParser extends BaseSingleSqlParser{
public InsertSelectSqlParser(String originalSql) {
    super(originalSql);
}
@Override
protected void initializeSegments() {
    segments.add(new SqlSegment("(insert into)(.+)( select )","[,]"));
    segments.add(new SqlSegment("(select)(.+)(from)","[,]"));
    segments.add(new SqlSegment("(from)(.+)( where | on | having | groups+by | orders+by | ENDOFSQL)","(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)"));
    segments.add(new SqlSegment("(where|on|having)(.+)( groups+by | orders+by | ENDOFSQL)","(and|or)"));
    segments.add(new SqlSegment("(groups+by)(.+)( orders+by| ENDOFSQL)","[,]"));
    segments.add(new SqlSegment("(orders+by)(.+)( ENDOFSQL)","[,]"));
}
}
package com.sitinspring.common.sqlparser.single;

import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
*
* 單句插入語句解析器
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class InsertSqlParser extends BaseSingleSqlParser{
public InsertSqlParser(String originalSql) {
    super(originalSql);
}
@Override
protected void initializeSegments() {
    segments.add(new SqlSegment("(insert into)(.+)([(])","[,]"));
    segments.add(new SqlSegment("([(])(.+)( [)] values )","[,]"));
    segments.add(new SqlSegment("([)] values [(])(.+)( [)])","[,]"));
}
@Override
public String getParsedSql() {
    String retval=super.getParsedSql();
    retval=retval+")";
    return retval;
}
}

複製代碼
複製代碼

package com.sitinspring.common.sqlparser.single;

public class NoSqlParserException extends Exception{
    private static final long serialVersionUID = 1L;
    NoSqlParserException()
    {
        
    }
    NoSqlParserException(String sql)
    {
        //調用父類方法
        super(sql);
    }
}

package com.sitinspring.common.sqlparser.single;

import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
*
* 單句查詢語句解析器
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class SelectSqlParser extends BaseSingleSqlParser{
public SelectSqlParser(String originalSql) {
    super(originalSql);
}
@Override
protected void initializeSegments() {
    segments.add(new SqlSegment("(select)(.+)(from)","[,]"));
    segments.add(new SqlSegment("(from)(.+)( where | on | having | group by | order by | ENDOFSQL)","(,| left join | right join | inner join )"));
    segments.add(new SqlSegment("(where|on|having)(.+)( group by | order by | ENDOFSQL)","(and|or)"));
    segments.add(new SqlSegment("(group by)(.+)( order by| ENDOFSQL)","[,]"));
    segments.add(new SqlSegment("(order by)(.+)( ENDOFSQL)","[,]"));
}
}
package com.sitinspring.common.sqlparser.single;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
//import com.sitinspring.common.sqlparser.single.NoSqlParserException;
/** *//**
* 單句Sql解析器製造工廠
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class SingleSqlParserFactory{
public static BaseSingleSqlParser generateParser(String sql)
{
    if(contains(sql,"(insert into)(.+)(select)(.+)(from)(.+)"))
    {
        return new InsertSelectSqlParser(sql);
    }
    else if(contains(sql,"(select)(.+)(from)(.+)"))
    {
        return new SelectSqlParser(sql);
    }
    else if(contains(sql,"(delete from)(.+)"))
    {
        return new DeleteSqlParser(sql);
    }
    else if(contains(sql,"(update)(.+)(set)(.+)"))
    {
        return new UpdateSqlParser(sql);
    }
    else if(contains(sql,"(insert into)(.+)(values)(.+)"))
    {
        return new InsertSqlParser(sql);
    }
    //sql=sql.replaceAll("ENDSQL", "");
    else
        return new InsertSqlParser(sql);
       //throw new NoSqlParserException(sql.replaceAll("ENDOFSQL", ""));//對異常的拋出
}
/** *//**
 * 看word是否在lineText中存在,支持正則表達式
 * @param sql:要解析的sql語句
 * @param regExp:正則表達式
 * @return
 */
private static boolean contains(String sql,String regExp){
    Pattern pattern=Pattern.compile(regExp,Pattern.CASE_INSENSITIVE);
    Matcher matcher=pattern.matcher(sql);
    return matcher.find();
}
}
package com.sitinspring.common.sqlparser.single;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** *//**
* Sql語句片段
*
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class SqlSegment{
private static final String Crlf = "@";
private static final String FourSpace = "  ";
/** *//**
 * Sql語句片段開頭部分
 */
private String start;
/** *//**
 * Sql語句片段中間部分
 */
private String body;
/** *//**
 * Sql語句片段結束部分
 */
private String end;
/** *//**
 * 用於分割中間部分的正則表達式
 */
private String bodySplitPattern;
/** *//**
 * 表示片段的正則表達式
 */
private String segmentRegExp;
/** *//**
 * 分割後的Body小片段
 */
private List<String> bodyPieces;
/** *//**
 * 構造函數
 * @param segmentRegExp 表示這個Sql片段的正則表達式
 * @param bodySplitPattern 用於分割body的正則表達式
 */
public SqlSegment(String segmentRegExp,String bodySplitPattern){
    start="";
    body="";
    end="";
    this.segmentRegExp=segmentRegExp;
    this.bodySplitPattern=bodySplitPattern;
    this.bodyPieces=new ArrayList<String>();
    
}
/** *//**
 * 從sql中查找符合segmentRegExp的部分,並賦值到start,body,end等三個屬性中
 * @param sql
 */
public void parse(String sql){
    Pattern pattern=Pattern.compile(segmentRegExp,Pattern.CASE_INSENSITIVE);
    for(int i=0;i<=sql.length();i++)
    {
     String shortSql=sql.substring(0, i);
     //測試輸出的子句是否正確
     System.out.println(shortSql);
     Matcher matcher=pattern.matcher(shortSql);
     while(matcher.find())
     {
         start=matcher.group(1);
         body=matcher.group(2);
         //測試body部分
         //System.out.println(body);
         end=matcher.group(3);
         //測試相應的end部分
         //System.out.println(end);
         parseBody();
         return;
     }
    }
}
/** *//**
 * 解析body部分
 *
 */
private void parseBody(){
    
    List<String> ls=new ArrayList<String>();
    Pattern p = Pattern.compile(bodySplitPattern,Pattern.CASE_INSENSITIVE);
    // 先清除掉前後空格
    body=body.trim();
    Matcher m = p.matcher(body);
    StringBuffer sb = new StringBuffer();
    boolean result = m.find();
    while(result)
    {
        m.appendReplacement(sb, m.group(0) + Crlf);
        result = m.find();
    }
    m.appendTail(sb);
    // 再按空格斷行
    String[] arr=sb.toString().split(" ");
    int arrLength=arr.length;
    for(int i=0;i<arrLength;i++)
    {
        String temp=FourSpace+arr[i];
        if(i!=arrLength-1)
        {
            //temp=temp+Crlf;
        }
        ls.add(temp);
    }
    bodyPieces=ls;
}
/** *//**
 * 取得解析好的Sql片段
 * @return
 */
public String getParsedSqlSegment(){
    StringBuffer sb=new StringBuffer();
    sb.append(start+Crlf);
    for(String piece:bodyPieces)
    {
        sb.append(piece+Crlf);
    }
    return sb.toString();
}

public String getBody()
{
    return body;
}

public void setBody(String body)
{
    this.body=body;
}

public String getEnd()
{
    return end;
}

public void setEnd(String end)
{
    this.end=end;
}

public String getStart()
{
    return start;
}


public void setStart(String start) 
{
    this.start=start;
}


}
package com.sitinspring.common.sqlparser.single;

import com.sitinspring.common.sqlparser.single.SqlSegment;
/** *//**
*
* 單句更新語句解析器
* @author 趙朝峯
*
* @since 2013-6-10
* @version 1.00
*/
public class UpdateSqlParser extends BaseSingleSqlParser{
public UpdateSqlParser(String originalSql) {
    super(originalSql);
}
@Override
protected void initializeSegments() {
    segments.add(new SqlSegment("(update)(.+)(set)","[,]"));
    segments.add(new SqlSegment("(set)(.+)( where | ENDOFSQL)","[,]"));
    segments.add(new SqlSegment("(where)(.+)( ENDOFSQL)","(and|or)"));
}
}
執行結果:自己寫了個測試的類
import java.util.List;

import com.sitinspring.common.sqlparser.single.*;
public class Test {
    /** *//**
    * 單句Sql解析器製造工廠
    * @author 趙朝峯
    *
    * @since 2013-6-10
    * @version 1.00
    */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
       //String test="select  a from  b " +
           //    "\n"+"where      a=b";
       //test=test.replaceAll("\\s{1,}", " ");
       //System.out.println(test);
       //程序的入口
        String testSql="select c1,c2,c3     from    t1,t2 where condi3=3 "+"\n"+"    or condi4=5 order by o1,o2";
        SqlParserUtil test=new SqlParserUtil();
        String result=test.getParsedSql(testSql);
        System.out.println(result);
       //List<SqlSegment> result=test.getParsedSqlList(testSql);//保存解析結果
    }

}
結果爲

select
     c1,
     c2,
     c3
from
      t1,
      t2
where
  condi3=3
  or
  condi4=5
order by
  o1,
     o2









發佈了8 篇原創文章 · 獲贊 69 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章