文章

RisingLight 源码分析-1

RisingLight 是一个专为教学目的设计的分析型数据库,采用 Rust 语言编写。

今天,我们将聚焦于其 parser(解析器)部分。在数据库处理一条 SQL 语句时,SQL 解析是执行流程中的第一个关键步骤。在 RisingLight 中,解析器的核心职责是将输入的 SQL 语句转化为抽象语法树(Abstract Syntax Tree, 简称 AST),为后续的查询优化和执行奠定基础。本项目中,我们采用了 sqlparser 这一流行的 Rust 库来完成 SQL 解析任务。

接下来,我们通过一个 sqlparser 的官方示例来具体了解其工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    use sqlparser::dialect::GenericDialect;
    use sqlparser::parser::Parser;

    let dialect = GenericDialect {}; // or AnsiDialect

    let sql = "SELECT * from test_table";

    let ast = Parser::parse_sql(&dialect, sql).unwrap();

    println!("AST: {:?}", ast);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Query(
    Query {
        with: None,
        body: Select(
            Select {
                select_token: TokenWithSpan {
                    token: Word(Word { value: "SELECT", ... }),
                    span: Span(Location(1,1)..Location(1,7))
                },
                distinct: None,
                top: None,
                top_before_distinct: false,
                projection: [
                    Wildcard(
                        WildcardAdditionalOptions {
                            wildcard_token: TokenWithSpan {
                                token: Mul, // The '*' character
                                span: Span(Location(1,8)..Location(1,9))
                            },
                            opt_ilike: None,
                            opt_exclude: None,
                            opt_except: None,
                            opt_replace: None,
                            opt_rename: None
                        }
                    )
                ],
                exclude: None,
                into: None,
                from: [
                    TableWithJoins {
                        relation: Table {
                            name: ObjectName([
                                Identifier(Ident {
                                    value: "test_table",
                                    quote_style: None,
                                    span: Span(Location(1,15)..Location(1,25))
                                })
                            ]),
                            alias: None,
                            args: None,
                            with_hints: [],
                            version: None,
                            with_ordinality: false,
                            partitions: [],
                            json_path: None,
                            sample: None,
                            index_hints: []
                        },
                        joins: []
                    }
                ],
                lateral_views: [],
                prewhere: None,
                selection: None,       // Corresponds to the WHERE clause
                group_by: Expressions([], []), // Corresponds to the GROUP BY clause
                cluster_by: [],
                distribute_by: [],
                sort_by: [],
                having: None,          // Corresponds to the HAVING clause
                named_window: [],
                qualify: None,
                window_before_qualify: false,
                value_table_mode: None,
                connect_by: None,
                flavor: Standard
            }
        ),
        order_by: None,        // Corresponds to the ORDER BY clause
        limit_clause: None,    // Corresponds to the LIMIT clause
        fetch: None,
        locks: [],
        for_clause: None,
        settings: None,
        format_clause: None,
        pipe_operators: []
    }
)

从这份详尽的抽象语法树输出中,我们可以清晰地提取出 SQL 语句的诸多关键信息:

SELECT 关键字的位置: 位于第 1 行的第 1 列至第 7 列。 投影方式: 采用通配符 * 查询所有字段。 查询目标表: test_table。 获取到这样的语法树之后,我们便能展开后续的处理工作。例如,我们可以根据 AST 中识别出的表名 test_table,到数据库的元数据信息中查询该表是否存在,并进一步验证其结构等。

本文由作者按照 CC BY 4.0 进行授权