zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Asp.net web api部署在某些服务器上老是404
    log4net记录日志到数据库自定义字段
    PIE-Basic 投影变换
    PIE-Basic 波段合成
    PIE-Basic 空间量测
    PIE-Basic 存储格式转换
    PIE-Basic 位深转换
    PIE-Basic 数据拉伸与显示
    PIE-Basic数据信息查看
    PIE-Basic 影像格式转换
  • 原文地址:https://www.cnblogs.com/zcftech/p/3131286.html
Copyright © 2011-2022 走看看