最近要實現一個簡易的數據庫系統,除了要考慮如何高效的存儲和訪問數據,建立表關系外,對基本的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