1作者: rustic-indian27 天前原帖
当我年轻的时候,我常常想,计算机是如何“理解”一种新的编程语言的。编译器对我来说充其量只是个模糊的概念,而解释型语言则像是魔法:你输入一些东西,机器就能以某种方式知道该怎么做。 SQL并不是直接编译成机器代码,但构建一个SQL引擎迫使你面对一个类似的问题:如何让一种语言(在我的案例中是Rust)理解并执行另一种语言(SQL),而它自己的编译器对此一无所知? 数据库并不是从一堆现有代码中发展而来的,而是源于一组概念:行、列、表达式、连接、聚合、索引、事务。 我的引擎的起点是一个受到SQLite和DuckDB强烈影响的通用SQL方言,因为它们都提供了一个名为SQLLogicTest的公共格式的大量测试。我理论上认为,一个能够通过这些测试的引擎将具有坚实的兼容性基础。我的目标不是发明一种新的SQL变体,而是支持这些系统已经运行的足够多的功能,以便我可以重用它们的测试套件和查询生态系统。 我没有自己编写解析器,而是使用了`sqlparser`,它将SQL转换为抽象语法树(AST),这是查询在内存中的图形表示。从那里,我将AST映射到我自己的Rust枚举和数据结构。这是SQL停止作为文本并开始作为引擎可以推理的一组概念的地方。 我没有从优化器开始,而是从测试工具开始。我将其连接起来以运行SQLite发布的SQLLogicTest测试集,并添加了一些对DuckDB风格测试的支持。今天,我运行的所有SQLite提供的测试都通过了。这并不意味着引擎是万无一失的;这仍然是早期的绿地代码。但这确实意味着引擎的行为是基于我自己头脑之外的东西:当测试失败时,是我的实现与已知结果的对比。这个测试套件已经成为事实上的规范;对计划器或执行器的任何更改都必须保持测试通过。 起初,我以为我会为每种想要支持的数据类型编写自己的计算内核。当你在某个领域缺乏经验时,你的思维会欺骗你,让你相信重新发明轮子是“正确”的唯一方式。最终,我将引擎迁移到了Apache Arrow的内存模型上。Arrow为我提供了列式表示和一组常见操作的内核。这样,我就可以专注于查询规划和将SQL语义映射到Arrow数组上,而不是手动编写和维护所有这些内容。 这个项目是我个人的努力。为了弥补这一点,我依赖于大型语言模型(LLMs)。在任何一天,我都会在Claude Sonnet 4.5和GPT-5.x Codex之间切换,以帮助构思新特性、推理生命周期问题或探索替代设计。它们的可靠性不足以让我完全信任,但足够快速,可以作为有噪音的、上下文感知的协同程序员。 这就形成了一种平衡:我有一个相对较大的测试集,必须保持通过,我希望新特性高效,并且不希望“聪明”的LLM生成的代码背后隐藏着微妙的正确性回归。测试形成了一种保护措施。如果建议的优化破坏了行为,它就会被抛弃。如果它通过了但看起来脆弱,我会对其进行重构。 我从未编写过编译器。这个项目是我所接近的最接近的东西。它介于查询引擎、解释器和一个关于如何通过借助其他系统的肩膀走得多远的课程之间:SQLite的测试、DuckDB的查询风格、Apache Arrow的内存模型,以及有时会以错误形式建议正确想法的LLMs。 如果没有其他,这个项目回答了我童年时的疑问。计算机并不是“自动理解”新语言的。总得有人来搭建那座桥。 (如果你喜欢这个故事,请搜索“rust-llkv”并给它一个星标。)