
【免费下载链接】toydbDistributed SQL database in Rust, written as an educational project项目地址https://gitcode.com/gh_mirrors/to/toydb点击查看免费下载本文是 toyDBRust 编写的分布式 SQL 数据库教学项目的 SQL 方言完整参考。toyDB 在 Raft 分布式共识与 MVCC 事务之上提供了一套贴近 PostgreSQL/SQLite 习惯的 SQL 接口支持建表约束、索引、四类连接、聚合与 GROUP BY、子句分页以及基于快照隔离的 ACID 事务。读完本文你将掌握 toyDB 的全部数据类型与字面量规则、运算符优先级与求值细节、十条 SQL 语句的完整语法与约束语义以及 MVCC 事务隔离与时间旅行AS OF SYSTEM TIME的准确行为。toyDB 的 SQL 引擎是一套标准的流水线Client → Session → Lexer → Parser → Planner → Optimizer → Executor → Storage其整体结构可参考 docs/architecture/sql.md。本文以官方 SQL 参考文档 docs/sql.md 为主体结合 src/sql 下的源码与 src/sql/testscripts 下的测试脚本展开。数据类型toyDB 只支持四种标量类型无复合类型对应枚举sql::types::DataType见 src/sql/types/value.rs类型别名说明BOOLEANBOOL逻辑真值TRUE/FALSEFLOATDOUBLE64 位有符号浮点数IEEE 754binary64编码支持 10⁻³⁰⁷ 到 10³⁰⁸ 的幅值与 53 位精度约 15 位有效数字以及特殊值INFINITY和NANINTEGERINT64 位有符号整数范围 ±(2⁶³-1)STRINGTEXT、VARCHARUTF-8 编码字符串此外特殊值NULL表示未知值遵循三值逻辑three-valued logic规则。从源码看值本身由sql::types::Value枚举承载Null、Boolean(bool)、Integer(i64)、Float(f64)、String(String)。有两处值得注意的底层细节存储层面的类型不可互换浮点值即使没有小数部分不能存入整数列反之亦然写入时会按列的数据类型做严格校验见 src/sql/types/schema.rs 的validate_row。注意这与下一节比较运算符中INTEGER与FLOAT可互换并不矛盾——互换性只发生在表达式求值阶段而非存储阶段。-0.0与-NaN的正向归一化为了在 KV 存储与索引查找中保持一致序列化时-0.0与-NaN会被当作正数处理src/sql/types/value.rs且代码内部NaN NaN、NULL NULL成立以支持检测、索引和排序SQL 语义下的 NULL/NaN 规则在表达式求值时另行实现src/sql/types/value.rs。SQL 词法与语法关键字关键字是 SQL 语句中具有特殊含义的保留字不区分大小写若想用作标识符必须用引号包围。完整列表如下AS,ASC,AND,BEGIN,BOOL,BOOLEAN,BY,COMMIT,CREATE,CROSS,DEFAULT,DELETE,DESC,DOUBLE,DROP,EXISTS,EXPLAIN,FALSE,FLOAT,FROM,GROUP,HAVING,IF,INDEX,INFINITY,INNER,INSERT,INT,INTEGER,INTO,IS,JOIN,KEY,LEFT,LIKE,LIMIT,NAN,NOT,NULL,OF,OFFSET,ON,ONLY,OR,ORDER,OUTER,PRIMARY,READ,REFERENCES,RIGHT,ROLLBACK,SELECT,SET,STRING,SYSTEM,TABLE,TEXT,TIME,TRANSACTION,TRUE,UNIQUE,UPDATE,VALUES,VARCHAR,WHERE,WRITE该列表与词法分析器中的sql::parser::Keyword枚举逐一对应见 src/sql/parser/lexer.rs。标识符标识符是表、列等数据库对象的名称。规则如下未加引号的标识符必须以 Unicode 字母开头后跟任意字母、数字和_的组合且不能是保留关键字用引号包围的标识符可以使用关键字和特殊字符其中用于转义双引号本身标识符一律转换为小写引号内的保留原大小写词法器行为见 src/sql/parser/lexer.rs。例如SELECT integer FROM t中integer因被引号包围而成为合法标识符测试证据见 src/sql/testscripts/queries/select。常量命名常量以下关键字直接求值为常量FALSE布尔假值INFINITY浮点无穷大NAN浮点 NaNnot a numberNULL未知值TRUE布尔真值。字符串字面量用单引号包围可包含任意合法 UTF-8 字符。单引号必须用额外的单引号转义即不支持其他转义序列。例如A string with quotes and emojis 词法器对字符串转义的处理见 src/sql/parser/lexer.rs。数字字面量纯数字序列0-9解析为 64 位有符号整数带小数点或科学计数法的数字解析为 64 位浮点数。支持的模式为999[.[999]][e[-]999]负号-是独立的前缀运算符词法器中前导符号不作为数字的一部分见 src/sql/parser/lexer.rs。表达式表达式可用于任何需要值的位置如SELECT的列、INSERT的值由常量、列引用、运算符调用和函数调用构成。列引用可以是未限定的name也可以用关系标识符加点号限定如person.name。未限定的标识符必须无歧义——若同一名字出现在多个表中规划器会报ambiguous column错误src/sql/planner/planner.rs。SQL 运算符逻辑运算符AND、OR、NOT对布尔操作数施加标准逻辑运算并遵循 SQL 三值逻辑NULL 参与。完整真值表如下ANDTRUEFALSENULLTRUETRUEFALSENULLFALSEFALSEFALSEFALSENULLNULLFALSENULLORTRUEFALSENULLTRUETRUETRUETRUEFALSETRUEFALSENULLNULLTRUENULLNULLNOTTRUEFALSEFALSETRUENULLNULL求值实现位于 src/sql/types/expression.rs注意其中的特例NULL AND FALSE FALSE、NULL OR TRUE TRUE与真值表一致。比较运算符比较运算符对同一数据类型的值进行比较成立返回TRUE否则返回FALSEINTEGER与FLOAT在比较时可互换STRING比较使用字符串的字节值即区分大小写且由于 UTF-8 码点关系B aFALSE视为小于TRUE与NULL比较恒为NULL即使NULL NULL。二元运算符相等如1 1得TRUE!不等如1 ! 2得TRUE大于如2 1得TRUE大于等于如1 1得TRUE小于如1 2得TRUE小于等于如1 1得TRUE。一元运算符IS NULL检查是否为NULL如NULL IS NULL得TRUEIS NOT NULL检查是否非NULL如TRUE IS NOT NULL得TRUEIS NAN检查是否为浮点NAN如NAN IS NAN得TRUE。对非浮点类型报错但NULL IS NAN得NULLIS NOT NAN检查是否非浮点NAN如3.14 IS NOT NAN得TRUE。上述IS NULL/NAN语义在 src/sql/types/expression.rs 中实现IS NAN作用于非浮点类型NULL 除外会返回cant use错误。数学运算符数学运算符对数值INTEGER或FLOAT操作数施加标准数学运算若任一操作数为FLOAT两个操作数都会转换为FLOAT结果为FLOAT若任一操作数为NULL结果为NULLINFINITY和NAN按 IEEE 754 规范处理对INTEGER操作数溢出、除零等失败条件会报错对FLOAT操作数这些情况按 IEEE 754 返回INFINITY或NAN。二元运算符加法如1 2得3-减法如3 - 2得1*乘法如3 * 2得6/除法如6 / 2得3^乘方如2 ^ 4得16%余数如8 % 3得2。注意这里采用余数而非模运算语义与 PostgreSQL 一致结果符号与被除数相同。一元运算符前缀恒等如1得1-前缀取负如- -2得2!后缀阶乘如5!得120。底层实现通过Value上的checked_add、checked_sub、checked_mul、checked_div、checked_pow、checked_rem完成src/sql/types/value.rs整数运算使用checked_*系列并显式报integer overflow错误整数除零报cant divide by zero阶乘只对非负整数定义src/sql/types/expression.rs。字符串运算符LIKE将字符串与模式比较%是任意多字符通配符_是单字符通配符匹配返回TRUE如abc LIKE a%得TRUE。实现通过正则表达式转换完成regex::escape后将%替换为.*、_替换为.且不支持转义_与%src/sql/types/expression.rs。运算符优先级运算符的优先级运算顺序如下从高到低优先级运算符结合性10,-前缀右9!后缀左8^右7*,/,%左6,-左5,,,左4,!,LIKE,IS左3NOT右2AND左1OR左优先级可用括号覆盖例如(1 2) * 3。这组优先级基本遵循 PostgreSQL其中IS与LIKE与同级类似 SQLite/MySQL由解析器通过优先级爬升算法precedence climbing实现源码与完整推导注释见 src/sql/parser/parser.rs。例如2 ^ 3 ^ 2 - 4 * 3会正确解析为(2 ^ (3 ^ 2)) - (4 * 3) 500因为^是右结合的。行为由测试脚本逐级验证见 src/sql/testscripts/expressions/op_precedence例如 2 ^ 3 ^ 2 (2 ^ 3) ^ 2 --- 512 64函数sqrt(expr)返回数值参数的平方根。底层为Expression::SquareRoot只接受非负INTEGER/FLOATNULL透传为NULLsrc/sql/types/expression.rs。聚合函数聚合函数对某个表达式跨行求值可配合GROUP BY分组并用HAVING过滤结果AVG(expr)数值的平均值。注意空分组下返回NULL累加器 count 为 0 时见 src/sql/execution/aggregator.rsCOUNT(expr)expr求值为非NULL的行数COUNT(*)用于统计全部行MAX(expr)按数据类型排序的最大值MIN(expr)按数据类型排序的最小值SUM(expr)数值之和。聚合累加器忽略NULL值src/sql/execution/aggregator.rsCOUNT(*)被特例化为常量TRUE计数src/sql/planner/planner.rs。聚合函数不能嵌套。SQL 语句BEGIN开启一个新事务。BEGIN [ TRANSACTION ] [ READ ONLY | READ WRITE ] [ AS OF SYSTEM TIME txn_id ]txn_id一个过去的事务 ID用于以只读方式运行时间旅行time-travel查询。会话层对BEGIN的处理见 src/sql/execution/session.rsREAD WRITE且带AS OF会被拒绝cant start read-write transaction in a given versionREAD ONLY AS OF SYSTEM TIME n则调用begin_as_of(n)。COMMIT提交当前活动事务。CREATE TABLE创建新表CREATE TABLE table_name ( [ column_name data_type [ column_constraint [ ... ] ] [ INDEX ] [, ... ] ] ) where column_constraint is: { NOT NULL | NULL | PRIMARY KEY | DEFAULT expr | REFERENCES ref_table | UNIQUE }table_name表名必须是合法标识符若同名表已存在则报错column_name列名必须是合法标识符且表内唯一data_type列的数据类型见数据类型一节NOT NULL列不允许NULL值NULL列允许NULL值默认行为PRIMARY KEY该列作为主键即行的主要标识符。每张表必须恰好有一个主键列且主键必须唯一、不可为空DEFAULT expr为列指定默认值当INSERT未提供该列值时代入。expr可以是任意合适类型的常量表达式如abc或1 2 * 3。对可空列默认值为NULL除非显式指定REFERENCES ref_table该列是引用ref_table主键的外键强制参照完整性UNIQUE列只能包含唯一互不相同的值。NULL值彼此不视为相等因此允许NULL的UNIQUE列可以包含多个NULL。PRIMARY KEY列隐式UNIQUEINDEX为该列创建索引。示例CREATE TABLE movie ( id INTEGER PRIMARY KEY, title STRING NOT NULL, release_year INTEGER INDEX, imdb_id STRING INDEX UNIQUE, bluray BOOLEAN NOT NULL DEFAULT TRUE )底层会做完整的模式校验src/sql/types/schema.rs必须有且仅有一个主键主键不可为空、不可再建二级索引UNIQUE与REFERENCES列必须建二级索引外键列类型必须与目标主键类型一致可空列必须带默认值。规划器还会为UNIQUE/REFERENCES列自动补充INDEX标记src/sql/planner/planner.rs。测试见 src/sql/testscripts/schema/create_table。DELETE删除表中的行DELETE FROM table_name [ WHERE predicate ]删除predicate求值为TRUE的行若不给出WHERE则删除全部行。table_name要删除数据的表不存在则报错predicate决定哪些行被删除的表达式必须求值为BOOLEAN或NULL否则报错。示例DELETE FROM movie WHERE release_year 2000 AND bluray FALSE执行时从扫描节点收集主键并批量删除src/sql/execution/executor.rs。DROP TABLE删除一张表及其全部数据。表不存在时报错除非给出IF EXISTSDROP TABLE [ IF EXISTS ] table_nametable_name要删除的表。EXPLAIN输出给定语句的执行计划EXPLAIN [ statement ]会话层对EXPLAIN返回优化后的计划Plan::build(...).optimize()src/sql/execution/session.rs不能嵌套EXPLAIN。INSERT向表中插入行INSERT INTO table_name [ ( column_name [, ... ] ) ] VALUES ( expression [, ... ] ) [, ... ]若给出列名必须提供数量一致的值若不给出列名值必须按表的列顺序给出省略的列取默认值若指定否则报错table_name目标表不存在则报错column_name要插入的列不存在则报错expression插入对应列的值表达式必须是常量表达式不能引用表列。执行器支持列映射与默认值填充两条路径src/sql/execution/executor.rs未提供的列优先取DEFAULT无默认值时报no value given for column ... with no default。示例INSERT INTO movie (id, title, release_year) VALUES (1, Sicario, 2015), (2, Stalker, 1979), (3, Her, 2013)ROLLBACK回滚当前活动事务。SELECT从表中选择行SELECT [ * | expression [ [ AS ] output_name [, ...] ] ] [ FROM from_item [, ...] ] [ WHERE predicate ] [ GROUP BY group_expr [, ...] ] [ HAVING having_expr ] [ ORDER BY order_expr [ ASC | DESC ] [, ...] ] [ LIMIT count ] [ OFFSET start ] where from_item is one of: table_name [ [ AS ] alias ] from_item join_type from_item [ ON join_predicate ] where join_type is one of: CROSS JOIN [ INNER ] JOIN LEFT [ OUTER ] JOIN RIGHT [ OUTER ] JOIN获取行或表达式数据来自table_name若给出或直接生成。expression要获取的表达式可以是简单的列名output_name输出列标识符默认取列名单列时否则无名字显示为?table_name取行的表alias表别名predicate只返回该表达式求值为TRUE的行group_expr分组聚合的表达式。非聚合的SELECT表达式必须引用group_expr中的列、与某个group_expr相同或有被group_expr列引用的output_namehaving_expr只返回该表达式求值为TRUE的聚合结果要求存在GROUP BY或聚合函数见 src/sql/planner/planner.rsorder_expr按此表达式排序可以是简单的列名count返回的最大行数必须是常量整数表达式start跳过的行数必须是常量整数表达式join_predicate只返回该表达式求值为TRUE的连接行。连接类型CROSS JOIN两表的笛卡尔积不接受连接谓词ON子句INNER JOIN两表笛卡尔积中join_predicate为TRUE的行LEFT OUTER JOIN按join_predicate连接的行对左表中没有匹配的每一行返回一行右表列全为NULL的行RIGHT OUTER JOIN与LEFT OUTER JOIN相同但左右表互换。规划阶段RIGHT OUTER JOIN被实现为左右交换的LEFT OUTER JOIN加列重映射src/sql/planner/planner.rs。执行阶段默认使用嵌套循环连接NestedLoopJoinersrc/sql/execution/join.rs优化器在等值连接条件下可将其改写为哈希连接HashJoinersrc/sql/execution/join.rs五个优化器常量折叠、过滤下推、索引查找、哈希连接、短路求值的注册顺序见 src/sql/planner/optimizer.rs。示例SELECT id, title, 2020 - released AS age FROM movies WHERE released 2000 AND ultrahd ORDER BY released DESC, title ASC LIMIT 10 OFFSET 10完整的 SELECT 行为含*展开、别名、限定/非限定列、歧义报错等可参考 src/sql/testscripts/queries/selectGROUP BY、HAVING、LIMIT/OFFSET、ORDER BY的专项测试分别在 src/sql/testscripts/queries/group_by、src/sql/testscripts/queries/having、src/sql/testscripts/queries/limit、src/sql/testscripts/queries/order。UPDATE更新表中的行UPDATE table_name SET column_name expression | DEFAULT [, ... ] [ WHERE predicate ]将column_name对应的列更新为expression的值作用于predicate为TRUE的所有行不给出WHERE则更新全部行。table_name要更新的表不存在则报错column_name要更新的列不存在则报错expression求值后写入对应行对应列的值。表达式可以引用列值且必须与更新列的数据类型一致。使用DEFAULT则写入该列的默认值若存在predicate决定哪些行被更新的表达式必须求值为BOOLEAN或NULL否则报错。示例UPDATE movie SET bluray TRUE WHERE release_year 2000 AND bluray FALSE事务与隔离级别toyDB 使用基于 MVCC 的快照隔离snapshot isolation支持 ACID 事务可防止以下异常脏写dirty writes、脏读dirty reads、丢失更新lost updates、模糊读fuzzy reads、读偏斜read skew和幻读phantom reads。由于未实现可串行化快照隔离写偏斜write skew异常是可能发生的。事务用BEGIN开启以COMMIT原子写入全部变更或ROLLBACK丢弃全部变更结束若并发事务之间发生冲突事务 ID 较小者胜出其余事务将因序列化错误而失败需要重试所有历史数据都会版本化并保留可通过BEGIN TRANSACTION READ ONLY AS OF SYSTEM TIME txn_id按给定事务 ID 查询过去的数据快照时间旅行查询事务内某条语句返回错误后该事务仍然有效由客户端决定后续操作。SQL 层面的事务行为由会话Session统一管理src/sql/execution/session.rsBEGIN/COMMIT/ROLLBACK直接在会话中处理普通语句在没有显式事务时自动使用隐式事务SELECT走只读隐式事务。会话析构时会自动回滚未结束的事务。src/sql/testscripts/transactions/isolation 给出了一个完整的隔离行为验证脚本事务c4开始时只看到此前已提交的c1写入与自己的写入看不到此后才提交的c2、未提交的c3与未来的c5而BEGIN READ ONLY AS OF SYSTEM TIME 4的c7即使在c4提交之后查询也始终只看到版本 4 时的快照c4: 1, a c4: 4, d c7: 1, a更底层的 MVCC 实现可进一步参考 src/storage/mvcc.rs 及其测试 src/storage/testscripts/mvcc其中每个异常类型anomaly_*都有对应脚本SQL 层写操作插入/更新/删除与索引、外键、唯一约束的联动可参考 src/sql/testscripts/writes 与 src/sql/testscripts/schema 目录下的脚本。上手验证如需在本地体验上述语法可先按 README.md 启动一个五节点集群再通过toysql客户端连接$ ./cluster/run.sh # 启动 5 节点SQL 端口 9601-9605 $ cargo run --release --bin toysql # 连接 node 1localhost:9601随后即可逐条尝试本文中的建表、插入、查询、连接、聚合与事务语句并用EXPLAIN观察执行计划。所有 SQL 功能的行为基准都固化在 src/sql/testscripts 下的 Goldenscript 测试脚本中可对照阅读以加深理解。赞分享【免费下载链接】toydbDistributed SQL database in Rust, written as an educational project项目地址https://gitcode.com/gh_mirrors/to/toydb点击查看免费下载相关推荐3个关键步骤让老Mac装上新版macOSOpenCore Legacy Patcher 上手指南3个关键步骤让老Mac装上新版macOSOpenCore Legacy Patcher 上手指南 目标只有一个把苹果已经不再支持的新版 macOS装到你那操作系统固件驱动开发Zeek 脚本语言完全参考类型系统、运算符、属性、声明语句与事件语义实战指南Zeek 脚本语言完全参考类型系统、运算符、属性、声明语句与事件语义实战指南 Zeek原名 Bro是一个强大的网络分析框架与常见的 IDS 不同它通过网络安全网络IDSProof of SQL 支持语法全解析数据类型、运算符与 SELECT 子句的可证明 SQL 规范Proof of SQL 支持语法全解析数据类型、运算符与 SELECT 子句的可证明 SQL 规范 本文是一份面向开发者的 PoSQLProof of S区块链密码学数据库后端上一篇5分钟终极指南用VoiceFixer让任何语音重获新生下一篇GoogleTest 定制注入点Customization Points全解在 PowerInfer 测试体系中定制 gtest 行为创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考