数据库中间件原理_数据库中间件是什么

数据库中间件原理_数据库中间件是什么- [1. 概述] – [2. Lexer 词法解析器] – [3. Token 词法标记] – [3.1 DefaultKeyword 词法关键词] – [3.2 Literals 词法字面量标记]

自我表扬:《Dubbo 实现原理与源码解析 —— 精品合集》


表扬自己:《D数据库实体设计合集》

摘要: 原创出处 http://www.iocoder.cn/Sharding-JDBC/sql-parse-1/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Sharding-JDBC 1.5.0 正式版


数据库中间件原理_数据库中间件是什么

🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

SQL 解析引擎,数据库中间件必备的功能和流程。Sharding-JDBC 在 1.5.0.M1 正式发布时,将 SQL 解析引擎从 Druid 替换成了自研的。新引擎仅解析分片上下文,对于 SQL 采用”半理解”理念,进一步提升性能和兼容性,同时降低了代码复杂度(不理解没关系,我们后续会更新文章解释该优点)。 国内另一款数据库中间件 MyCAT SQL 解析引擎也是 Druid,目前也在开发属于自己的 SQL 解析引擎。

可能有同学看到SQL 解析会被吓到,请淡定,耐心往下看。《SQL 解析》内容我们会分成 5 篇相对简短的文章,让大家能够相对轻松愉快的去理解:

  1. 词法解析
  2. 插入 SQL 解析
  3. 查询 SQL 解析
  4. 更新 SQL 解析
  5. 删除 SQL 解析

数据库中间件原理_数据库中间件是什么

SQL 解析引擎parsing 包下,如上图所见包含两大组件:

  1. Lexer:词法解析器。
  2. Parser:SQL解析器。

两者都是解析器,区别在于 Lexer 只做词法的解析,不关注上下文,将字符串拆解成 N 个词法。而 Parser 在 Lexer 的基础上,还需要理解 SQL 。打个比方:

SQL :SELECT * FROM t_user  
Lexer :[SELECT] [ * ] [FROM] [t_user]  
Parser :这是一条 [SELECT] 查询表为 [t_user] ,并且返回 [ * ] 所有字段的 SQL。

🙂不完全懂?没关系,本文的主角是 Lexer,我们通过源码一点一点理解。一共 1400 行左右代码左右,还包含注释等等,实际更少噢。

2. Lexer 词法解析器

Lexer 原理顺序顺序顺序 解析 SQL,将字符串拆解成 N 个词法。

核心代码如下:

// Lexer.java
public class Lexer {

    /** * 输出字符串 * 比如:SQL */
    @Getter
    private final String input;
    /** * 词法标记字典 */
    private final Dictionary dictionary;
    /** * 解析到 SQL 的 offset */
    private int offset;
    /** * 当前 词法标记 */
    @Getter
    private Token currentToken;

    /** * 分析下一个词法标记. * * @see #currentToken * @see #offset */
    public final void nextToken() {
        skipIgnoredToken();
        if (isVariableBegin()) { // 变量
            currentToken = new Tokenizer(input, dictionary, offset).scanVariable();
        } else if (isNCharBegin()) { // N\
            currentToken = new Tokenizer(input, dictionary, ++offset).scanChars();
        } else if (isIdentifierBegin()) { // Keyword + Literals.IDENTIFIER
            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()) { // 字符串,例如:"abc"
            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();
        // System.out.println("| " + currentToken.getLiterals() + " | " + currentToken.getType() + " | " + currentToken.getEndPosition() + " |");
    }
    /** * 跳过忽略的词法标记 * 1. 空格 * 2. SQL Hint * 3. SQL 注释 */
    private void skipIgnoredToken() {
        // 空格
        offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        // SQL Hint
        while (isHintBegin()) {
            offset = new Tokenizer(input, dictionary, offset).skipHint();
            offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        }
        // SQL 注释
        while (isCommentBegin()) {
            offset = new Tokenizer(input, dictionary, offset).skipComment();
            offset = new Tokenizer(input, dictionary, offset).skipWhitespace();
        }
    }
}

通过 #nextToken() 方法,不断解析出 Token(词法标记)。我们来执行一次,看看 SQL 会被拆解成哪些 Token。

SQLSELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.user_id=? AND o.order_id=?

literals TokenType类 TokenType值 endPosition
SELECT DefaultKeyword SELECT 6
i Literals IDENTIFIER 8
. Symbol DOT 9
* Symbol STAR 10
FROM DefaultKeyword FROM 15
t_order Literals IDENTIFIER 23
o Literals IDENTIFIER 25
JOIN DefaultKeyword JOIN 30
t_order_item Literals IDENTIFIER 43
i Literals IDENTIFIER 45
ON DefaultKeyword ON 48
o Literals IDENTIFIER 50
. Symbol DOT 51
order_id Literals IDENTIFIER 59
= Symbol EQ 60
i Literals IDENTIFIER 61
. Symbol DOT 62
order_id Literals IDENTIFIER 70
WHERE DefaultKeyword WHERE 76
o Literals IDENTIFIER 78
. Symbol DOT 79
user_id Literals IDENTIFIER 86
= Symbol EQ 87
? Symbol QUESTION 88
AND DefaultKeyword AND 92
o Literals IDENTIFIER 94
. Symbol DOT 95
order_id Literals IDENTIFIER 103
= Symbol EQ 104
? Symbol QUESTION 105
Assist END 105

眼尖的同学可能看到了 Tokenizer。对的,它是 Lexer 的好基佬,负责分词

我们来总结下,Lexer#nextToken() 方法里,使用 #skipIgnoredToken() 方法跳过忽略的 Token,通过 #isXXXX() 方法判断好下一个 Token 的类型后,交给 Tokenizer 进行分词返回 Token。‼️此处可以考虑做个优化,不需要每次都 new Tokenizer(...) 出来,一个 Lexer 搭配一个 Tokenizer。


由于不同数据库遵守 SQL 规范略有不同,所以不同的数据库对应不同的 Lexer。

数据库中间件原理_数据库中间件是什么

子 Lexer 通过重写方法实现自己独有的 SQL 语法。

3. Token 词法标记

上文我们已经看过 Token 的例子,一共有 3 个属性:

  • TokenType type :词法标记类型
  • String literals :词法字面量标记
  • int endPosition :literals 在 SQL 里的结束位置

TokenType 词法标记类型,一共分成 4 个大类:

  • DefaultKeyword :词法关键词
  • Literals :词法字面量标记
  • Symbol :词法符号标记
  • Assist :词法辅助标记

数据库中间件原理_数据库中间件是什么

3.1 DefaultKeyword 词法关键词

数据库中间件原理_数据库中间件是什么

不同数据库有自己独有的_词法关键词_,例如 MySQL 熟知的分页 Limit。

我们以 MySQL 举个例子,当创建 MySQLLexer 时,会加载 DefaultKeyword 和 MySQLKeyword( OracleLexer、PostgreSQLLexer、SQLServerLexer 同 MySQLLexer )。核心代码如下:

// MySQLLexer.java
public final class MySQLLexer extends Lexer {

    /** * 字典 */
    private static Dictionary dictionary = new Dictionary(MySQLKeyword.values());
    
    public MySQLLexer(final String input) {
        super(input, dictionary);
    }
}

// Dictionary.java
public final class Dictionary {

    /** * 词法关键词Map */
    private final Map<String, Keyword> tokens = new HashMap<>(1024);
    
    public Dictionary(final Keyword... dialectKeywords) {
        fill(dialectKeywords);
    }

    /** * 装上默认词法关键词 + 方言词法关键词 * 不同的数据库有相同的默认词法关键词,有有不同的方言关键词 * * @param dialectKeywords 方言词法关键词 */
    private void fill(final Keyword... dialectKeywords) {
        for (DefaultKeyword each : DefaultKeyword.values()) {
            tokens.put(each.name(), each);
        }
        for (Keyword each : dialectKeywords) {
            tokens.put(each.toString(), each);
        }
    }
}

Keyword 与 Literals.IDENTIFIER 是一起解析的,我们放在 Literals.IDENTIFIER 处一起分析。

3.2 Literals 词法字面量标记

Literals 词法字面量标记,一共分成 6 种:

  • IDENTIFIER :词法关键词
  • VARIABLE :变量
  • CHARS :字符串
  • HEX :十六进制
  • INT :整数
  • FLOAT :浮点数

3.2.1 Literals.IDENTIFIER 词法关键词

词法关键词。例如:表名,查询字段 等等。

解析 Literals.IDENTIFIER 与 Keyword 核心代码如下:

// Lexer.java
private boolean isIdentifierBegin() {
   return isIdentifierBegin(getCurrentChar(0));
}
private boolean isIdentifierBegin(final char ch) {
   return CharType.isAlphabet(ch) || '`' == ch || '_' == ch || '$' == ch;
}

// Tokenizer.java
/** * 扫描标识符. * * @return 标识符标记 */
public Token scanIdentifier() {
   // `字段`,例如:SELECT `id` FROM t_user 中的 `id`
   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);
   // 处理 order / group 作为表名
   if (isAmbiguousIdentifier(literals)) {
       return new Token(processAmbiguousIdentifier(offset + length, literals), literals, offset + length);
   }
   // 从 词法关键词 查找是否是 Keyword,如果是,则返回 Keyword,否则返回 Literals.IDENTIFIER
   return new Token(dictionary.findTokenType(literals, Literals.IDENTIFIER), literals, offset + length);
}
/** * 计算到结束字符的长度 * * @see #hasEscapeChar(char, int) 处理类似 SELECT a AS `b``c` FROM table。此处连续的 "``" 不是结尾,如果传递的是 "`" 会产生误判,所以加了这个判断 * @param terminatedChar 结束字符 * @return 长度 */
private int getLengthUntilTerminatedChar(final char terminatedChar) {
   int length = 1;
   while (terminatedChar != charAt(offset + length) || hasEscapeChar(terminatedChar, offset + length)) {
       if (offset + length >= input.length()) {
           throw new UnterminatedCharException(terminatedChar);
       }
       if (hasEscapeChar(terminatedChar, offset + length)) {
           length++;
       }
       length++;
   }
   return length + 1;
}
/** * 是否是 Escape 字符 * * @param charIdentifier 字符 * @param offset 位置 * @return 是否 */
private boolean hasEscapeChar(final char charIdentifier, final int offset) {
   return charIdentifier == charAt(offset) && charIdentifier == charAt(offset + 1);
}
private boolean isIdentifierChar(final char ch) {
   return CharType.isAlphabet(ch) || CharType.isDigital(ch) || '_' == ch || '$' == ch || '#' == ch;
}
/** * 是否是引起歧义的标识符 * 例如 "SELECT * FROM group",此时 "group" 代表的是表名,而非词法关键词 * * @param literals 标识符 * @return 是否 */
private boolean isAmbiguousIdentifier(final String literals) {
   return DefaultKeyword.ORDER.name().equalsIgnoreCase(literals) || DefaultKeyword.GROUP.name().equalsIgnoreCase(literals);
}
/** * 获取引起歧义的标识符对应的词法标记类型 * * @param offset 位置 * @param literals 标识符 * @return 词法标记类型 */
private TokenType processAmbiguousIdentifier(final int offset, final String literals) {
   int i = 0;
   while (CharType.isWhitespace(charAt(offset + i))) {
       i++;
   }
   if (DefaultKeyword.BY.name().equalsIgnoreCase(String.valueOf(new char[] {charAt(offset + i), charAt(offset + i + 1)}))) {
       return dictionary.findTokenType(literals);
   }
   return Literals.IDENTIFIER;
}

3.2.2 Literals.VARIABLE 变量

变量。例如:SELECT @@VERSION

解析核心代码如下:

// Lexer.java
/** * 是否是 变量 * MySQL 与 SQL Server 支持 * * @see Tokenizer#scanVariable() * @return 是否 */
protected boolean isVariableBegin() {
   return false;
}

// Tokenizer.java
/** * 扫描变量. * 在 MySQL 里,@代表用户变量;@@代表系统变量。 * 在 SQLServer 里,有 @@。 * * @return 变量标记 */
public Token scanVariable() {
   int length = 1;
   if ('@' == charAt(offset + 1)) {
       length++;
   }
   while (isVariableChar(charAt(offset + length))) {
       length++;
   }
   return new Token(Literals.VARIABLE, input.substring(offset, offset + length), offset + length);
}

3.2.3 Literals.CHARS 字符串

字符串。例如:SELECT "123"

解析核心代码如下:

// Lexer.java
/** * 是否 N\ * 目前 SQLServer 独有:在 SQL Server 中處理 Unicode 字串常數時,必需為所有的 Unicode 字串加上前置詞 N * * @see Tokenizer#scanChars() * @return 是否 */
private boolean isNCharBegin() {
   return isSupportNChars() && 'N' == getCurrentChar(0) && '\'' == getCurrentChar(1);
}
private boolean isCharsBegin() {
   return '\'' == getCurrentChar(0) || '\"' == getCurrentChar(0);
}

// Tokenizer.java
/** * 扫描字符串. * * @return 字符串标记 */
public Token scanChars() {
   return scanChars(charAt(offset));
}
private Token scanChars(final char terminatedChar) {
   int length = getLengthUntilTerminatedChar(terminatedChar);
   return new Token(Literals.CHARS, input.substring(offset + 1, offset + length - 1), offset + length);
}

3.2.4 Literals.HEX 十六进制

// Lexer.java
/** * 是否是 十六进制 * * @see Tokenizer#scanHexDecimal() * @return 是否 */
private boolean isHexDecimalBegin() {
   return '0' == getCurrentChar(0) && 'x' == getCurrentChar(1);
}

// Tokenizer.java
/** * 扫描十六进制数. * * @return 十六进制数标记 */
public Token scanHexDecimal() {
   int length = HEX_BEGIN_SYMBOL_LENGTH;
   // 负数
   if ('-' == charAt(offset + length)) {
       length++;
   }
   while (isHex(charAt(offset + length))) {
       length++;
   }
   return new Token(Literals.HEX, input.substring(offset, offset + length), offset + length);
}

3.2.5 Literals.INT 整数

整数。例如:SELECT * FROM t_user WHERE id = 1

Literals.INT 与 Literals.FLOAT 是一起解析的,我们放在 Literals.FLOAT 处一起分析。

3.2.6 Literals.FLOAT 浮点数

浮点数。例如:SELECT * FROM t_user WHERE id = 1.0。 浮点数包含几种:”1.0″,”1.0F”,”7.823E5″(科学计数法)。

解析核心代码如下:

// Lexer.java
/**
* 是否是 数字
* '-' 需要特殊处理。".2" 被处理成省略0的小数,"-.2" 不能被处理成省略的小数,否则会出问题。
* 例如说,"SELECT a-.2" 处理的结果是 "SELECT" / "a" / "-" / ".2"
*
* @return 是否
*/
private boolean isNumberBegin() {
   return CharType.isDigital(getCurrentChar(0)) // 数字
           || ('.' == getCurrentChar(0) && CharType.isDigital(getCurrentChar(1)) && !isIdentifierBegin(getCurrentChar(-1)) // 浮点数
           || ('-' == getCurrentChar(0) && ('.' == getCurrentChar(0) || CharType.isDigital(getCurrentChar(1))))); // 负数
}

// Tokenizer.java
/**
* 扫描数字.
* 解析数字的结果会有两种:整数 和 浮点数.
*
* @return 数字标记
*/
public Token scanNumber() {
   int length = 0;
   // 负数
   if ('-' == charAt(offset + length)) {
       length++;
   }
   // 浮点数
   length += getDigitalLength(offset + length);
   boolean isFloat = false;
   if ('.' == charAt(offset + length)) {
       isFloat = true;
       length++;
       length += getDigitalLength(offset + length);
   }
   // 科学计数表示,例如:SELECT 7.823E5
   if (isScientificNotation(offset + length)) {
       isFloat = true;
       length++;
       if ('+' == charAt(offset + length) || '-' == charAt(offset + length)) {
           length++;
       }
       length += getDigitalLength(offset + length);
   }
   // 浮点数,例如:SELECT 1.333F
   if (isBinaryNumber(offset + length)) {
       isFloat = true;
       length++;
   }
   return new Token(isFloat ? Literals.FLOAT : Literals.INT, input.substring(offset, offset + length), offset + length);
}

这里要特别注意下:“-“。在数字表达实例,可以判定为 负号 和 减号(不考虑科学计数法)。

  • “.2” 解析结果是 “.2”
  • “-.2” 解析结果不能是 “-.2″,而是 “-” 和 “.2″。

3.3 Symbol 词法符号标记

词法符号标记。例如:”{“, “}”, “>=” 等等。

解析核心代码如下:

// Lexer.java
/** * 是否是 符号 * * @see Tokenizer#scanSymbol() * @return 是否 */
private boolean isSymbolBegin() {
   return CharType.isSymbol(getCurrentChar(0));
}

// CharType.java
/** * 判断是否为符号. * * @param ch 待判断的字符 * @return 是否为符号 */
public static boolean isSymbol(final char ch) {
   return '(' == ch || ')' == ch || '[' == ch || ']' == ch || '{' == ch || '}' == ch || '+' == ch || '-' == ch || '*' == ch || '/' == ch || '%' == ch || '^' == ch || '=' == ch
           || '>' == ch || '<' == ch || '~' == ch || '!' == ch || '?' == ch || '&' == ch || '|' == ch || '.' == ch || ':' == ch || '#' == ch || ',' == ch || ';' == ch;
}

// Tokenizer.java
/** * 扫描符号. * * @return 符号标记 */
public Token scanSymbol() {
   int length = 0;
   while (CharType.isSymbol(charAt(offset + length))) {
       length++;
   }
   String literals = input.substring(offset, offset + length);
   // 倒序遍历,查询符合条件的 符号。例如 literals = ";;",会是拆分成两个 ";"。如果基于正序,literals = "<=",会被解析成 "<" + "="。
   Symbol symbol;
   while (null == (symbol = Symbol.literalsOf(literals))) {
       literals = input.substring(offset, offset + --length);
   }
   return new Token(symbol, literals, offset + length);
}

3.4 Assist 词法辅助标记

Assist 词法辅助标记,一共分成 2 种:

  • END :分析结束
  • ERROR :分析错误。

666. 彩蛋

知识星球

老铁,是不是比想象中简单一些?!继续加油写 Parser 相关的文章!来一波微信公众号关注吧。


Sharding-JDBC 正在收集使用公司名单:传送门。🙂 你的登记,会让更多人参与和使用 Sharding-JDBC。Sharding-JDBC 也会因此,能够覆盖更广的场景。登记吧,少年!


我创建了一个微信群【源码圈】,希望和大家分享交流读源码的经验。
读源码先难后易,掌握方法后,可以做更有深度的学习。
而且掌握方法并不难噢。
加群方式:微信公众号发送关键字【qun】。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13217.html

(0)

相关推荐

  • Oracle学习(七) — PL/SQL(二) 游标、储存过程、自定义函数、触发器

    Oracle学习(七) — PL/SQL(二) 游标、储存过程、自定义函数、触发器1、PL/SQL — 游标 1.1、什么是游标 当在PL/SQL中使用SQL语句时,Oracle会为其分配上下文区域,这是一段私有的内存区域,用于暂时保存SQL语句影响到的数据。游标是指向这段内存…

    2023-03-08
    159
  • MongoDB学习笔记:应用程序设计「建议收藏」

    MongoDB学习笔记:应用程序设计「建议收藏」本文更新于2021-12-11,使用MongoDB 4.4.5。 范式化与反范式化 范式化(normalization)将数据分散到多个集合,不同集合之间相互引用数据。反范式化(denormaliza

    2023-05-07
    149
  • SQL语句初始[通俗易懂]

    SQL语句初始[通俗易懂] SQL概述 asad …

    2023-03-21
    147
  • Mysql自序整理集[通俗易懂]

    Mysql自序整理集[通俗易懂]1、事务 mysql事务是用于处理操作量大、复杂性高的数据 1. 事务特性 原子性:保证每个事务所有操作要么全部完成或全部不完成,不可能停滞在中间环节;如事务在执行过程中出现错误,则会回滚到事务开始之

    2023-05-06
    124
  • 把短视频上传到其他平台,怎么转换格式?

    把短视频上传到其他平台,怎么转换格式?**把短视频上传到其他平台,怎么转换格式?**小编最近在人民日报公众号上看到一段短视频,是关于一个网友把一名闪送员接到一个单子把药品送上长城,还背着受伤的女孩走了一段路的内容。在网上这个短视频也被转…

    2023-03-05
    149
  • Python程序求列表平均值

    Python程序求列表平均值在Python中,我们可以很方便地求一个列表中所有数据的平均值。平均值是一组数据的中心位置,也就是把所有数据加起来,再除以数据的个数。

    2024-08-19
    28
  • Python实现打开文件夹的方法

    Python实现打开文件夹的方法在Python程序中,我们可以使用os模块来调用操作系统自带的命令,实现文件夹的打开、删除、添加等操作。其中,打开文件夹是我们最为常见的操作之一。示例代码如下:

    2024-03-23
    77
  • Python创始人Guido van Rossum

    Python创始人Guido van RossumPython是世界上最流行的编程语言之一,众所周知,Python是由荷兰计算机科学家Guido van Rossum在1989年圣诞节期间创造的。今天,Python已经成为数据科学、机器学习、人工智能等领域的主要语言之一。Guido van Rossum创建的Python不仅仅是一种编程语言,更是一个思想的象征,尽管最初的Python只有一些简单的功能,但是它的简洁、易读性、多功能性和其他的特点为它在经历了这三十年的发展之后,成为了一个极受欢迎的编程语言。

    2024-06-18
    47

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注