搜索/在不改变查询含义的前提下,扩展支持多种方言的 SQL 编译器

在不改变查询含义的前提下,扩展支持多种方言的 SQL 编译器

修改把 SQL 转成 AST、再为其他数据库生成 SQL 的编译器时使用。它会先找到负责的层,在方言之间保持含义不变,用 AST 构建器而不是字符串拼接生成 SQL,并测试真实执行和速度。

要使用这个AI规则
## 修改前先找到负责该行为的编译器层 将系统视为各层职责分明的编译器: - 词法分析器将SQL文本转换为类型化词元,并允许方言覆盖词法映射; - 语法分析器采用递归下降法构建抽象语法树(AST),并使用专门的匹配、游标、列表和包装辅助函数; - 表达式类表示具有语义的SQL概念,并支持遍历和转换; - 生成器使用简单的转换映射或专用生成方法,将表达式递归转换回SQL; - 方言模块通过功能标志、词元集合、语法分析映射和生成映射扩展公共基类; - 优化规则按照有意安排的顺序规范化并重写AST; - 模式信息和类型信息支持优化、转译和血缘分析; - 血缘分析将输出列连接到上游表达式和表。 修改行为前先确定负责该行为的层。不要为了修复一层而把逻辑泄漏到另一层,应继续将词法分析、语法分析、语义表示、优化和生成彼此分离。 保持语法分析器与生成器的对称性:语法分析把词元映射为表达式,生成则把表达式映射为SQL。如果语法分析器有意将无法识别的文本回退为通用命令节点,不要悄然把这种保留机制变成不完整的语法分析。 ## 使用仓库自带的测试和检查命令,覆盖纯实现和原生扩展 编辑前阅读仓库的贡献指南和架构指南。使用其中记录的目标来完成基础安装、开发安装、可选原生扩展安装、提交前设置、完整测试、单元测试、样式检查、类型检查和基准测试。 区分纯语言实现与任何可选的编译扩展。先运行与修改直接相关的模式,再执行修改所需的更广泛检查。修改影响原生加速时,应同时测试纯实现和编译路径,不要假定二者行为相同。 迭代期间通过运行特定测试文件、类或方法来精确定位失败。提交前运行仓库要求的完整检查目标,包括格式化、静态检查、纯运行时测试,以及适用时的原生扩展测试。 对性能敏感的工作使用语法分析和优化基准。应比较配置相同的对象,不要为了有利的微基准结果而牺牲语义正确性。 ## 把 SQL 转成另一种方言时保持同样的含义 将AST视为语义上的唯一依据。把源SQL解析为语义表达式,再从这些表达式生成目标方言;保持含义,而不是保持源文本的写法。可以尽力保留注释,但语义等价性优先。 只有在语义确实一致时,才把多个方言共享的功能加入公共基类。方言特有的语法或行为只能加入相应方言。不要分散添加名称检查,应使用明确的功能标志、保留词元集合、函数或语句映射,以及表达式到SQL的映射。 保持优化规则有意安排的顺序。限定名称、类型标注、谓词或投影下推以及简化可能依赖较早的步骤和模式信息。应把优化器视为逻辑重写器,而不是物理查询规划器。 功能依赖数据库结构时,应明确提供模式信息。缺少所需的表和列元数据时,不要声称限定名称、类型推断、优化或列血缘分析是可靠的。 ## 用 AST 构建器和模板生成 SQL,而不是拼接字符串 创建通用函数表达式前,先在表达式目录中查找现有的专用类。只有不存在专用语义类时,才能使用通用匿名表达式。 优先选择受支持的最简单生成机制: 1. 对于单个复杂表达式类型,实现可自动发现的生成器方法 `<lowercase_expression_name>_sql`。 2. 转换映射仅用于简单的单行重命名、Lambda,或有意设置多个入口的函数。 3. 在生成器方法内部,使用生成器的函数辅助方法,不要仅为序列化而构造函数节点。 4. 对数组、布尔表达式、条件分支、类型转换、列、NULL、算术、索引和比较使用表达式构建器与运算符。 5. 对于复杂输出,解析SQL模板,并在复制的AST中替换占位符。 不要用字符串插值拼接SQL片段。AST构建器和模板能够保留引号处理、转义、嵌套和方言行为,而手工拼接的字符串通常会破坏这些属性。 ## 在类型标注之后再检查类型,并在运行时处理 NULL 区分语法类型检查与语义类型检查。只有在判断节点本身是否为字符串字面量并提取其字面值时,才使用 `is_string` 之类的字面量谓词。检查列、函数结果或其他表达式推断出的语义类型时,应使用 `is_type()`。 如果语义行为依赖类型,应在调用 `is_type()` 前运行类型标注。依赖类型的转译测试必须为AST添加类型标注;否则,由于类型从未填充,正确的语义检查也可能看起来失败。 确认节点是数值字面量后,使用表达式的转换方法(例如 `to_py()`)提取数值。不要手工去除内部原始文本中的引号再进行类型转换。 不要仅通过检查NULL字面量节点,就在转译时解析SQL的 `NULL` 行为。应生成包含运行时 `IS NULL` 逻辑的SQL,使列、参数、表达式和NULL字面量都遵循相同语义。 查找祖先节点时,应包含查询作用域边界,避免搜索越界进入父查询。目标方言无法表示特定参数时,使用框架规定的不支持参数声明机制。 ## 为每种方言写测试,并在真实数据库上运行 把测试加入负责受影响方言或编译器层的测试套件。对于应保持原样往返的语法,使用往返断言;当同一个语义表达式必须为不同目标生成不同SQL时,使用跨方言断言。 不要重复已有测试已经证明的行为。适用时,应针对新的语义区别添加聚焦用例,包括可空性、类型、引号、作用域、不支持的参数或方言差异。 确保放在数据库专用套件中的测试,确实会针对相应数据库或兼容引擎执行。如果规则依赖运行时行为,仅有生成的字符串不能构成证据。 对于类型敏感的输出,同时测试字面量和有类型的非字面量表达式。对于原生加速,确认其与纯实现一致。对于优化器修改,加入考虑模式信息的用例,并验证规则顺序不会改变含义。 ## 每次改动只为一个目的,并让它便于审查 将每项修改限制在一个明确界定的目的内。为非平凡行为添加测试,API变化时更新公开文档字符串,遵循仓库的拉取请求和提交约定,并在提交前运行要求的完整检查。 移除未使用的导入、变量和死代码。不要为有意移除的代码添加兼容性垫片。在保持可读的前提下,优先使用内联表达式而不是不必要的临时变量,但不要把逻辑压缩到语义阶段变得不清楚。 只为不明显的逻辑使用注释,并且只为复杂行为或公开API添加文档字符串。除非任务明确涉及许可,否则不要修改许可证文件或许可配置。 审查修改中是否存在意外的方言耦合、重复的表达式类型、手工构建的SQL、缺失的类型标注、仅在编译时处理NULL、作用域泄漏,以及未经测试的原生行为或数据库特有行为。 ## 做优化时保留原生加速路径和解析器的设计 即使主要实现使用高级语言,也应将性能视为产品功能。保留可选的原生加速路径,并对影响词法分析、语法分析、生成或优化的修改进行基准测试。 不要以推测能够优化为由,将有意采用的递归下降语法分析器替换为Pratt解析。语法分析策略是一项架构选择,而不是可互换的速度设置。 如果代码库已经记录了推荐替代方案,应避免使用已知会干扰编译路径的抽象。例如,应选择简单的联合类型和结构化鸭子类型,而不是不受支持的协议抽象。 只有在语法分析、AST构建、生成、方言行为和测试都保持正确后才能优化。任何改变语义、丢失引号处理、错误处理 `NULL`,或使纯模式与编译模式出现差异的加速,都是回归。