Shading-jdbc源码分析-sql词法解析

栏目: 数据库 · 发布时间: 7年前

内容简介:前有芋艿大佬已经发过相关分析的文章,自己觉的源码总归要看一下,然后看了就要记录下来(记性很差...),所以就有了这篇文章(以后还要继续更:smile:) ,希望我们都能在看过文章后能够有不一样的收获。声明:本文基于1.5.M1版本首先我们来看下解析sql的过程中用到的类做一个解释:

前有芋艿大佬已经发过相关分析的文章,自己觉的源码总归要看一下,然后看了就要记录下来(记性很差...),所以就有了这篇文章(以后还要继续更:smile:) ,希望我们都能在看过文章后能够有不一样的收获。

声明:本文基于1.5.M1版本

相关的UML类图

Shading-jdbc源码分析-sql词法解析
Shading-jdbc源码分析-sql词法解析
Shading-jdbc源码分析-sql词法解析
Shading-jdbc源码分析-sql词法解析

解析:

首先我们来看下解析 sql 的过程中用到的类做一个解释:

  • TokenType:衍生了多个子类,用来标记sql拆分过程中,每个被拆分的词的类型(比如select属于KeyWord,";"属于Symbol)
  • Lexer:sql具体的解析类,通过调用nextToken()方法分析sql每个词的类型;
  • Tokenizer:具体的标记类,标记具体的词,配合Lexer的nextToken()方法使用
  • Token:标记后的结果,type:具体的词类型、literals:具体的词、endPosition:这个词在sql中的最后位置(index)
@Test
    public void assertNextTokenForOrderBy() {
        Lexer lexer = new Lexer("SELECT * FROM ORDER  ORDER \t  BY XX DESC", dictionary);
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, DefaultKeyword.SELECT, "SELECT");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, Symbol.STAR, "*");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, DefaultKeyword.FROM, "FROM");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, Literals.IDENTIFIER, "ORDER");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, DefaultKeyword.ORDER, "ORDER");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, DefaultKeyword.BY, "BY");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, Literals.IDENTIFIER, "XX");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, DefaultKeyword.DESC, "DESC");
        //lexer.nextToken();
        LexerAssert.assertNextToken(lexer, Assist.END, "");
    }
复制代码

上面是项目中的一段测试用例,我们以这个用例来分析。

  • 第一次调用nextToken()
/**
     * 分析下一个词法标记.
     */
    public final void nextToken() {
        skipIgnoredToken();
        if (isVariableBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanVariable();
        } else if (isNCharBegin()) {
            currentToken = new Tokenizer(input, dictionary, ++offset).scanChars();
        } else if (isIdentifierBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanIdentifier();
        } else if (isHexDecimalBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanHexDecimal();
        } else if (isNumberBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanNumber();
        } else if (isSymbolBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanSymbol();
        } else if (isCharsBegin()) {
            currentToken = new Tokenizer(input, dictionary, offset).scanChars();
        } else if (isEnd()) {
            currentToken = new Token(Assist.END, "", offset);
        } else {
            currentToken = new Token(Assist.ERROR, "", offset);
        }
        offset = currentToken.getEndPosition();
    }
复制代码
  • 先走skipIgnoredToken();
  1. 跳过空格
  2. 跳过以/*!开头的(Mysql是这样)的字符,对于不同数据库。isHintBegin实现了不同的处理
  3. 跳过注释
private void skipIgnoredToken() {
        offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        while (isHintBegin()) {
            offset = new Tokenizer(input, dictionary, offset).skipHint();
            offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        }
        while (isCommentBegin()) {
            offset = new Tokenizer(input, dictionary, offset).skipComment();
            offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        }
    }
复制代码

这里我们以跳过空格为例来展开说明:

从传入的offset标志位开始,循环判断sql语句中对应位置的字符是不是空格,直到不是空格就退出,返回最新位置的offset

/**
     * 跳过空格. 
     * 
     * @return 跳过空格后的偏移量
     */
    public int skipWhitespace() {
        int length = 0;
        while (CharType.isWhitespace(charAt(offset + length))) {
            length++;
        }
        return offset + length;
    }
    
    private char charAt(final int index) {
        return index >= input.length() ? (char) CharType.EOI : input.charAt(index);
    }
    /**
     * 判断是否为空格.
     * 
     * @param ch 待判断的字符
     * @return 是否为空格
     */
    public static boolean isWhitespace(final char ch) {
        return ch <= 32 && EOI != ch || 160 == ch || ch >= 0x7F && ch <= 0xA0;
    }
复制代码
  • 第二步 从最新位置的offset开始,继续判断是否是变量,这里以 mysql 为例,开始的单词是‘SELECT’,所以进入第三步
/**
    这是mysql的实现
  **/
@Override
    protected boolean isVariableBegin() {
        return '@' == getCurrentChar(0);
    }
复制代码
  • 第三步 判断是否是NChar,false,进入第四步
private boolean isNCharBegin() {
        return isSupportNChars() && 'N' == getCurrentChar(0) && '\'' == getCurrentChar(1);
    }
复制代码
  • 第四步 判断是否是标识符 true
  1. 扫描标识符
  2. 循环判断当前的标识符是不是字符,直到不是字符
  3. 截取这个字符串
  4. 判断是否是双关词汇(group、order)
  5. 如果4符合,则进一步做特殊处理
  6. 构造Token返回
private boolean isIdentifierBegin() {
        return isIdentifierBegin(getCurrentChar(0));
    }
 private boolean isIdentifierBegin(final char ch) {
        return CharType.isAlphabet(ch) || '`' == ch || '_' == ch || '$' == ch;
    }
   /**
     * 判断是否为字母.
     *
     * @param ch 待判断的字符
     * @return 是否为字母
     */
    public static boolean isAlphabet(final char ch) {
        return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z';
    }   
    
复制代码
/**
     * 扫描标识符.
     *
     * @return 标识符标记
     */
    public Token scanIdentifier() {
        if ('`' == charAt(offset)) {
            int length = getLengthUntilTerminatedChar('`');
            return new Token(Literals.IDENTIFIER, input.substring(offset, offset + length), offset + length);
        }
        int length = 0;
        while (isIdentifierChar(charAt(offset + length))) {
            length++;
        }
        String literals = input.substring(offset, offset + length);
        if (isAmbiguousIdentifier(literals)) {
            return new Token(processAmbiguousIdentifier(offset + length, literals), literals, offset + length);
        }
        return new Token(dictionary.findTokenType(literals, Literals.IDENTIFIER), literals, offset + length);
    }
复制代码
  • 返回最终的Token,赋值给currentToken,更新offset,此时的Token内容如下。第一个 “SELECT” 就解析出来了,后面的单词继续调用nextToken(),方法差不多,区别就是词法的类型不一样,走的判断可能逻辑会不同,后面有兴趣的可以自己跟着代码去看看。
Shading-jdbc源码分析-sql词法解析

最后

小尾巴走一波,欢迎关注我的公众号,不定期分享编程、投资、生活方面的感悟:)

Shading-jdbc源码分析-sql词法解析

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms to Live By

Algorithms to Live By

Brian Christian、Tom Griffiths / Henry Holt and Co. / 2016-4-19 / USD 30.00

A fascinating exploration of how insights from computer algorithms can be applied to our everyday lives, helping to solve common decision-making problems and illuminate the workings of the human mind ......一起来看看 《Algorithms to Live By》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具