SQLFluff 规则系统内部机制:`sqlfluff.core.rules.base` 基类架构与自定义规则开发指南
SQLFluff 规则系统内部机制sqlfluff.core.rules.base基类架构与自定义规则开发指南【免费下载链接】sqlfluffA modular SQL linter and auto-formatter with support for multiple dialects and templated code.项目地址: https://gitcode.com/GitHub_Trending/sq/sqlfluff本篇文章以 SQLFluff 官方文档中 Internal API - Rules 页面为骨架展开。该页面通过 Sphinxautomodule指令将sqlfluff.core.rules.base模块的完整 docstring 渲染为 API 参考文档是面向插件开发者、规则开发者以及 SQLFluff 贡献者的核心参考页。本文在此基础上深入源码系统梳理规则引擎的设计思想、基类体系、注册装配流程并给出可落地的自定义规则开发路径。SQLFluff 的规则系统负责在解析器产出的语法树上“爬行”并对特定节点求值最终产出违规报告与自动修复建议。sqlfluff.core.rules.base就是这一切的根基BaseRule基类、LintResult结果对象、RuleSet/RulePack注册装配机制以及元类层面的命名规范与文档自动生成逻辑全部集中于此。读完本文你将理解 SQLFluff 的规则是如何被定义、注册、过滤、实例化与执行的并具备动手编写一条自定义规则的完整知识储备。文档定位这份 Internal API 页面向谁服务在 docs/source/reference/internals/index.rst 中rules与config、functional、reflow一起组成了 “Internal API” 章节。该章节的开篇说明明确指出Anything within this section should only be necessary for people who are developing plugins or rules to interact with SQLFluff on a deeper level or people whove decided to help the project by contributing to SQLFluff.也就是说本文所述的规则基类体系是 SQLFluff 的“内部 API”普通用户一般只需要在配置文件中启用/禁用规则而插件作者、规则开发者与项目贡献者才需要深入这层 API。这与 docs/source/guides/setup/developing_custom_rules.rst 等开发向导互为表里——前者讲“怎么用 API 写规则”本页则讲“这套 API 本身长什么样、为什么这么设计”。规则引擎的核心设计思想sqlfluff.core.rules.base模块开头的模块级 docstring 一句话概括了规则引擎的本质Rules crawl through the trees returned by the parser and evaluate particular rules. The intent is that it should be possible for the rules to be expressed as simply as possible, with as much of the complexity abstracted away.即规则在解析器返回的语法树上“爬行”crawl并对特定节点求值evaluate。设计目标是把尽可能多的复杂性抽象掉让每条规则的表达尽量简单。该 docstring 还给出了一个对规则开发者至关重要的定位约定The evaluation function should take enough arguments that it can evaluate the position of the given segment in relation to its neighbors, and that the segment which finally triggers the error, should be the one that would be corrected OR if the rule relates to something that is missing, then it should flag on the segment FOLLOWING, the place that the desired element is missing.触发错误的锚点段anchor应当是“将被修复的那个段”让报错位置与修复位置对齐如果规则针对的是“缺失的元素”例如缺空格、缺逗号则应该在缺失位置之后紧邻的那个段上打点报错而不是指向“本来应该存在却不存在”的虚无位置。这个约定贯穿了LintResult.anchor、LintFix.anchor以及后续锚点自动调整逻辑的设计。RuleMetaclass命名规范、代码提取与文档自动生成BaseRule使用自定义元类RuleMetaclass见 src/sqlfluff/core/rules/base.py来驱动三个关键机制规则命名校验、代码与描述提取、docstring 自动富化。规则命名规范自动提取 rule code规则类必须遵循严格的命名格式元类通过预编译正则Rule_?([A-Z]{1}[a-zA-Z])?_([A-Z0-9]{4})校验并提取信息核心规则Rule_LLNN其中L为字母、N为两位数字例如Rule_CP01CP CaPitalisation兼容旧格式单字母 三位数字的LNNN例如历史上L010插件规则Rule_PluginName_LL23格式PluginName部分会被拼进 code形成如CP01_plugin_name的插件专属规则码。如果类名不符合规范元类会抛出SQLFluffUserError。规则描述description则取类 docstring 的第一行将反引号替换为单引号后截取。docstring 自动富化为 Sphinx 文档服务元类会扫描规则类的 docstring在预编译正则匹配到的Anti-pattern / note / Configuration标记处自动插入以下内容块若is_fix_compatible True标注 “This rule issqlfluff fixcompatible.”Name规则的name属性如capitalisation.keywordsAliases规则别名列表如L010Groups规则所属分组如all、core、capitalisationConfiguration遍历config_keywords从config_info中取出每个配置项的 definition 与 validation自动生成配置文档。这正是automodule渲染出的规则文档中 “Configuration” 一节内容的来源。同时元类还会从父类继承groups与config_keywords避免 CP02 这类继承规则在文档中丢失分组信息校验name必须是全小写 snake_case可用.表达命名空间如layout.spacing若插件规则在插件加载完成前被导入会输出性能警告日志提示插件应在get_rules()方法内导入规则定义。BaseRule一切规则的基类BaseRulesrc/sqlfluff/core/rules/base.py定义了规则的完整生命周期先看它暴露的类级属性规则子类通过覆写这些属性来声明自身行为属性默认值作用name规则的人类可读名称如layout.spacing作为配置查找引用groups()规则分组元组用于批量选择规则必须包含allaliases()规则别名通常用于兼容旧规则码如 LT01 的L001code/description由元类自动设置规则码与描述不应手动赋值is_fix_compatibleFalse是否支持sqlfluff fix自动修复config_keywords[]该规则支持的自定义配置项名列表crawl_behaviour必须覆写规则使用的爬虫crawler实例lint_phasemain规则执行阶段post表示“不期望再触发下游规则”的规则如大小写修复在主阶段首轮与第二轮 linter pass 中运行_works_on_unparsableTrue是否在无法解析的段上工作_adjust_anchorsFalse是否对修复锚点做自动上提hoisting调整targets_templatedFalse规则是否针对模板化代码段template_safe_fixesFalse声明该规则的修复在模板元素附近是安全的可跳过默认安全检查实例化时__init__所有从配置传入的 kwargs 会被逐一写入实例的__dict__供规则方法直接访问同时会校验每个config_keywords声明的选项确实出现在 kwargs 中否则抛出ValueError并提示补充到default_config.cfg或插件配置。规则求值接口_evaldef _eval(self, context: RuleContext) - EvalResultType: Evaluate this rule against the current context. Returns: :obj:LintResult, list of :obj:LintResult or :obj:None. _eval是每条规则必须覆写的方法基类实现直接抛出NotImplementedError。它接收一个RuleContext返回三种结果之一None无问题也意味着不传递 memory单个LintResult一个违规可能附带修复与 memorylist[LintResult]多个违规memory 取自列表最后一个元素。方法名刻意用_eval而非eval是为了配合 Sphinx autodoc 让文档自动生成更友好。规则开发者需要显式声明自己依赖的 context 字段同时为了兼容性应接受**kwargs。主执行入口crawlcrawl()是规则对整棵语法树执行一次的入口返回四元组(violations, raw_stack, fixes, memory)。其流程为构造根RuleContext携带 dialect、fix 标志、templated_file、文件路径、segment、config尝试Rust 原生分发见下文若core.use_rust_rules开启且解析产物带 Rust arena_rs_tree则调用_eval_rust否则进入 Python 路径遍历self.crawl_behaviour.crawl(root_context)产生的每个子上下文将上一段的memory注入当前上下文后调用_eval对每个LintResult依次执行_adjust_anchors_for_fixes锚点调整与_process_lint_result模板安全校验、noqa 掩码过滤、不可解析过滤最终汇聚为SQLLintError列表与LintFix列表。crawl内部对规则执行过程中的任何异常做了可恢复处理异常不会被直接抛出导致整个文件 lint 失败而是被记录为一条SQLLintError描述中包含 “Unexpected exception”并提示用户可用-- noqa: code忽略保证用户至少能得到部分结果。bdb.BdbQuit与KeyboardInterrupt除外二者会被重新抛出。模板安全与锚点调整_process_lint_result在最终上报前会检查违规锚点及其所有父段是否通过 crawler 的passes_filter即是否位于不可解析区域discard_unsafe_fixes负责丢弃“不安全”的修复触及模板化代码的修复fix.has_template_conflicts以及跨越多个模板块block的修复对应 issue #3079会被整体丢弃——此时违规仍会报告但被标记为不可自动修复_adjust_anchors_for_fixes与_choose_anchor_segment实现锚点“上提”hoist当规则如 LT02/LT05 这类空白处理规则返回的锚点位于语法树过深的叶节点时会沿父链向上寻找“允许非代码端点”can_start_end_non_code的祖先把create_before/create_after修复挂到更可靠的锚点上避免破坏解析树结构。Rust 原生分发_eval_rust作为实验性加速路径规则可以覆写_eval_rust在core.use_rust_rules开启且解析产生 Rust arena 时一次性对整个 arena 计算全部LintResult结果与_eval结果走相同的后处理管线。未实现 Rust 路径的规则返回None或使用 Python 解析器无 arena时会自动回退到 Python 爬行路径。CP01/CP03/CP04 等大小写类规则已提供该实现见 src/sqlfluff/rules/capitalisation/CP01.py。LintResult一次求值的结果LintResultsrc/sqlfluff/core/rules/base.py是_eval的返回值对象构造函数参数如下参数说明anchor表示问题位置的段。anchor 为None意味着“没有问题”fixes可修复此问题的LintFix列表缺省表示该问题需手动修复memory规则的工作记忆会被传递给下一个被爬行的段description覆盖规则默认描述的自定义问题描述source结果来源标识字符串在 reflow 等大型库中用于追踪结果来源LintResult.to_linting_error(rule)将结果转换为SQLLintErrorsrc/sqlfluff/core/errors.py使用description or rule.description作为违规描述若 anchor 为空则返回None不产生违规。其__repr__以LintResult(empty)表示空结果带修复时以NF后缀标明修复数量方便在日志中排查。LintFix修复操作对象LintFixsrc/sqlfluff/core/rules/fix.py描述一次修复动作核心字段为edit_typecreate_before、create_after、replace、delete四种之一其余取值直接抛ValueErroranchor修复应用位置的段——删除时表示被删段创建时表示插入位置原有元素将被推到编辑之后替换时表示被替换段editreplace/create类型要插入的段的可迭代对象source提供代码来源的段linter 依赖它防止从模板区域复制内容。构造时的关键校验与行为创建类型修复必须非空且每个 edit 段的 raw 非空replace修复不得“用段替换它自己”edit 段会被deep copy并剥离预设的pos_marker位置标记由后续 realignment 统一计算避免污染原始解析结构对替换成同 raw 的“纯 source 编辑”is_just_source_edit有专门识别用于安全地传播模板 source fix。to_dict()将修复序列化为结构化 dict含行号/列号是sqlfluff fix输出与测试断言的基础。RuleContext传给_eval的上下文RuleContextsrc/sqlfluff/core/rules/context.py)是一个 dataclass分两组字段文件内不变dialect、fix是否处于修复模式、templated_file、path文件路径、configFluffConfig文件内随爬行变化segment当前段、parent_stack从根到当前段的祖先链、raw_stack截至当前的原始段序列、memory规则任意存储、segment_idx当前段在父节点中的索引。并提供了两个便捷属性siblings_pre当前段之前的兄弟段与siblings_post之后的兄弟段。在SegmentSeekerCrawler中为性能考虑会原地修改同一个RuleContext实例避免每个段都新建对象产生海量短生命周期对象并在递归返回时重置字段。爬虫体系crawlers.py规则在树上的“爬行策略”被抽象为爬虫crawler类src/sqlfluff/core/rules/crawlers.pyBaseCrawler抽象基类核心方法是passes_filter(segment)——默认拒绝unparsable类型的段除非works_on_unparsableTrue该过滤在爬行与结果处理两处都会用到RootOnlyCrawler只在文件根段上求值一次用于 LT01 这类“全局视角”的规则SegmentSeekerCrawler(types, provide_raw_stack, allow_recurse)按段类型集合高效搜索目标段通过descendant_type_set求交集做激进剪枝——子树中不含目标类型时直接跳过整棵子树allow_recurseFalse时一旦某段匹配其子段不再返回适合“一个根段在同一轮求值中检查所有子段”的场景provide_raw_stackTrue时维护原始段栈涉及较多元组操作默认关闭以节省开销ParentOfSegmentCrawler搜索“直接子段包含指定类型”的父段基于direct_descendant_type_set匹配。规则通过覆写crawl_behaviour选择爬虫CP01 使用SegmentSeekerCrawler({keyword, binary_operator, date_part})并排除literal及其 data_type 等父类型LT01 则使用RootOnlyCrawler并以ReflowSequence统一处理整段间距。RuleSet/RulePack/RuleManifest注册、过滤与装配注册RuleSet.register标准规则集在 src/sqlfluff/core/rules/init.py 的_load_standard_rules()中构建创建RuleSet(namestandard, config_infoget_config_info())后遍历插件管理器pluggy的get_rules()hook把每个规则类注册进标准集。get_ruleset()每次调用都会重建并返回一份 copy以支持运行时动态规则变更。register装饰器src/sqlfluff/core/rules/base.py完成代码冲突检测同一规则码二次注册直接抛错强制要求规则属于all组将类元数据封装为RuleManifestcode / name / description / groups / aliases / rule_class存入注册表。myruleset.register class Rule_LT01(BaseRule): Description of rule. def eval(self, **kwargs): return LintResult()引用映射codes names groups aliasesrule_reference_map()构建规则引用映射先以 code 自映射再并入 name 映射、group 映射、alias 映射。优先级为 codes names groups aliases——当出现碰撞时低优先级引用如别名与规则名重复会被丢弃并给出警告。该映射让用户在配置中既可用规则码LT01、也可用规则名layout.spacing、分组capitalisation、别名L001乃至glob 通配LT0*来批量选择规则。装配get_rulepackget_rulepack(config)是配置到规则实例的关键转换函数流程为校验通用配置项的取值合法性_validate_config_options非法值抛SQLFluffUserError校验配置文件中是否有针对未知规则的配置段发现则告警提示正确的sqlfluff:rules:name写法计算 allowlist默认取全部规则码与 denylist并对未知引用发出警告通过_expand_rule_refs用fnmatch展开 glob 引用最终得到过滤后的规则码列表对每条规则合并通用规则配置与非 dict 的特定规则配置段注入code与经过变量替换.format(**kwargs)的description最后实例化规则类返回RulePack(rules, reference_map)。RulePack是“过滤后待应用的规则包”之所以在主进程中完成过滤与实例化是为了在多进程模式下让用户自定义规则可以被安全地引用pickle 传递。其reference_map与codes()方法配合 noqa 注释解析使用。实战结合实例理解一条规则的全貌以 CP01src/sqlfluff/rules/capitalisation/CP01.py为例看一条真实规则的声明方式class Rule_CP01(BaseRule): Inconsistent capitalisation of keywords. **Anti-pattern** In this example, select is in lower-case whereas FROM is in upper-case. .. code-block:: sql select a FROM foo **Best practice** Make all keywords either in upper-case or in lower-case. .. code-block:: sql SELECT a FROM foo -- Also good select a from foo name capitalisation.keywords aliases (L010,) groups: tuple[str, ...] (all, core, capitalisation) is_fix_compatible True lint_phase post crawl_behaviour SegmentSeekerCrawler({keyword, binary_operator, date_part}) config_keywords [capitalisation_policy, ignore_words, ignore_words_regex]可以看到 docstring 中Anti-pattern反例与Best practice正例的双栏结构——这正是元类插入 Name/Aliases/Groups/Configuration 元数据的锚点位置也是 Sphinx 文档中规则示例的来源。而 LT01src/sqlfluff/rules/layout/LT01.py则展示了“一个规则整合多个历史规则”的别名模式aliases (L001, L005, L006, L008, L023, L024, L039, L048, L071)其_eval借助 src/sqlfluff/utils/reflow/sequence.py 的ReflowSequence完成间距重置。如果你要写一条自定义规则推荐路径为阅读 docs/source/guides/setup/developing_custom_rules.rst 与 docs/source/guides/contributing/rules.rst参考官方示例插件 plugins/sqlfluff-plugin-example在插件的get_rules()中返回规则类规则码按Rule_PluginName_LL23命名按本文所述约定实现_eval或_eval_rust、声明crawl_behaviour、groups、config_keywords在插件的default_config.cfg或pyproject.toml中补充规则配置项并在config_info中登记定义与校验取值见 src/sqlfluff/core/rules/config_info.py 中的STANDARD_CONFIG_INFO_DICT如ignore_words、blocked_words等通用项用 test/fixtures/rules/std_rule_cases 中的 YAML 用例驱动测试参考 test/rules/std_test.py 与 test/rules/yaml_test_cases_test.py。小结SQLFluff 规则系统的精巧之处在于“约定优于配置”与“抽象掉复杂度”元类自动完成命名解析、描述提取、文档富化与配置校验让每条规则的 docstring 同时成为用户文档BaseRule._eval 爬虫把“在树上找什么、怎么看”拆分为两个正交维度规则开发者只需关注求值逻辑LintResult/LintFix/RuleContext构成一套声明式的结果协议模板安全与锚点修正等易错细节由框架统一兜底RuleSet/RulePack以 codes/names/groups/aliases 多级引用 glob 展开完成规则的过滤、配置注入与实例化支撑了配置文件中的灵活选择语法。对想要深入 SQLFluff 内部或为其编写插件的开发者而言docs/source/reference/internals/rules.rst 所指向的sqlfluff.core.rules.base模块是必读的起点配合 functional函数式遍历 API与 reflow重排引擎两个相邻的 Internal API 页面即可完整掌握 SQLFluff 的规则开发全景。【免费下载链接】sqlfluffA modular SQL linter and auto-formatter with support for multiple dialects and templated code.项目地址: https://gitcode.com/GitHub_Trending/sq/sqlfluff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考