「Rust 重写 sqlite」解析输入命令

「这是我参与11月更文挑战的第 13 天,活动详情查看:2021最后一次更文挑战


构建项目

image.png

你需要做的第一件事是在你的Cargo.toml文件中添加该依赖项:

[dependencies]
rustyline = "9.0.0"
rustyline-derive = "0.5.0"
thiserror = "1.0.24"
sqlparser = "0.12.0"
serde = { version = "1.0.123", features = ["derive", "rc"] }
structopt = "0.3.25"
prettytable-rs = "0.8.0"
log = "0.4.14"
env_logger = "0.9.0"
复制代码

上面我也提到我要利用 rustyline 的一些特性,这就是为什么我们有 rustyline-derive 依赖的原因,这个 crate 实际上只是一些过程宏,帮助我们实现必要的特性,以便用rustyline向我们的REPL程序添加验证、提示和完成等东西。

structopt crate是一个struct解析器,也相当成熟,它解决了我们为创建自己的CLI解析器而必须编写的大量模板代码,因为内含有 clap crate(命令行解析器)。现在,它实际上没有做什么,只是在我们输入 rust-sqlite --help 时显示一些帮助信息。

另外两个依赖项:log,env_logger 是为了使整个应用程序的日志记录更容易,比如说,为我们管理不同的日志级别。

main.rs

为了保持代码的简洁和可读性,我会尽可能地保持我的主文件的简洁。在 main.rs 你可能已经注意到了,我在第2行声明了一个 mod repl; 。这是因为我把与我们的REPL程序部分有关的大部分业务逻辑移到了一个不同的模块。这样,我们的main.rs可以保持干净,易于阅读。

所以,我导入了一个结构和一个函数 (from repl module)

use repl::{REPLHelper, get_config};
复制代码

rustylinehelper struct 负责实现我们希望在REPL程序上实现的所有行为。

例如,我们正在使用 Validator trait,它可以在用户输入时实时验证输入。我们还使用了 Hinter trait,负责在用户输入命令时提供提示。

所有与rustyline REPL的实现和行为相关的代码都是一个单独的模块。同时我认为这些辅助模块会给用户在使用程序带来更好的感觉,另外 sqlite3 也提供了这些,我们既然是复刻也要提供。

交互分析

上次我们所做的是一个简单的CLI应用程序,它将用于外部命令和一些附属功能,还有一个简单的REPL,它还需要采取一个简单的命令来优雅地退出应用程序。

首先,我们要对输入进行解析,以便能够区分输入是 MetaCommand 还是 SQLCommand 。MetaCommand以 . 开始,并采取直接行动,如 .open/.help/.exit 。而SQLCommand是啥,这个你应该知道的。

我们要做的第二步是能够解析每个命令类型并采取适当的行动。

现在,我们开始往数据库方面靠近。REPL会接收命令,然后区分不同的SQL语句,并将它们的组成部分分解成若干部分,准备执行。因此,下一次我们可以把重点放在获取解析后的SQL语句并执行它。

SQLite的前端部分解析SQL语句,对其进行优化(优化器参与),正如在前一篇文章中提到的,内部会生成等价的内部表示形态,称为:字节码

然后,字节码被传递给后端的虚拟机,由它来执行。而对于前端解析SQL → 字节码,整个步骤也会分成几步:

  1. 词法分析: Tokenizer
  2. 语法分析: Parser
  3. 生成最终字节码: Code Generator

像这样把逻辑分成几步,有几个好处:

  1. 降低了每个部分的复杂性(例如,虚拟机不担心语法错误,因为在前面的词法分析已经过滤了)。
  2. 允许一次性编译普通查询,并缓存字节码以提高性能。

猜你喜欢

转载自juejin.im/post/7032598859071094814
今日推荐