Apache DataFusion 语义规范解读:逻辑/物理平面不变量与输出字段名生成规则
大数据数据分析后端【免费下载链接】datafusionApache DataFusion SQL Query Engine项目地址https://gitcode.com/gh_mirrors/datafu/datafusion点击查看免费下载本文围绕 Apache DataFusion 官方规格说明Specification体系系统讲解其中两份核心规格文档逻辑/物理平面不变量Invariants与输出字段名语义Output Field Name Semantics。这两份规格用于在开发与代码评审过程中消除歧义回答DataFusion 的查询结果在什么条件下是正确、可预期的这一核心问题。读完本文你将掌握 DataFusion 逻辑计划、物理计划与 Arrow 批数据之间必须满足的一致性约束理解字段名从 SQL 或 DataFrame API 生成的具体规则并能定位到 源码 中的对应实现进行验证。规格说明文档机制DataFusion 如何正式化语义在 规格说明索引页 中DataFusion 社区明确说明通过规格文档specification documents来正式化formalize部分语义与行为。这些规格的价值在于——当开发或代码评审过程中出现歧义时可以将其作为参考基准references来裁决分歧。规格文档的存放与演进规则如下当前激活的规格清单toctree包含两份invariants —— 逻辑/物理平面的不变量output-field-name-semantic —— 输出字段名语义规格文档集中存放在docs/source/contributor-guide/specification/目录社区欢迎任何人提议修改现有规格或创建新规格作为项目演进的一部分。这意味着规格不是一份静态的死文档而是与代码库并行演进的活文档。下面分别深入解读两份规格。第一部分Invariants —— 逻辑与物理平面必须遵守的约束设计动机动态类型系统下的类型不变量DataFusion 的计算模型构建在 Arrow 的动态类型对象Array之上。Array提供Array::as_any接口可以将自身向下转型downcast为静态类型版本如Int32ArrayDataFusion 通过Array::data_type来对物理操作执行相应的 downcasting。为什么采用动态类型系统因为被执行的查询并不总是在编译期已知而是在运行时查询时才确定——这是构建 DataFusion 这类嵌入式查询引擎的前提。在动态类型接口中由开发者负责强制类型不变量规格声明了其中的一部分不变量让用户知道查询可以预期什么结果也让 DataFusion 开发者知道在编码层面必须强制什么。需要特别说明的是文档明确承认其中一些不变量目前尚未强制执行currently not enforced。符号约定为了精确描述规格定义了如下记号物理字段Field / Physical field由名称、arrow::DataType和可空标志nullability布尔值表示值是否可以为 null组成的三元组记作PF(name, type, nullable)逻辑字段Logical field带关系relation名的字段记作LF(relation, name, type, nullable)投影计划Projected plan以投影节点为根节点的计划逻辑模式Logical schema逻辑字段的向量由逻辑计划使用物理模式Physical schema物理字段的向量由物理计划和 Arrow RecordBatch 共同使用阅读不变量时需要注意一个隐含前提规格原话由于函数的输出模式依赖于其参数的输入模式例如min、plus结果模式只能基于一组已知的输入模式TableProvider推导同理函数的模式也依赖于已注册的函数注册表例如my_op返回 u32 还是 u64。因此下文所有相同模式same schema均指在给定的数据源与函数注册表下的相同模式。逻辑平面Logical中的组件函数Function一个知道自身合法输入逻辑字段、并能从参数逻辑字段推导输出逻辑字段的对象。函数输出字段是输入字段的函数logical_field(lf1: LF, lf2: LF, ...) - LF规格给出的示例plus(a,b) - LF(None, {a} Plus {b}, d(a.type,b.type), a.nullable | b.nullable)其中d是输入类型到输出类型的映射函数当前实现为get_supertypelength(a) - LF(None, length({a}), u32, a.nullable)计划Plan由其他计划和函数组成的树例如Projection c1 c2, c1 - c2 AS sum12; Scan c1 as u32, c2 as u64知道如何推导自身的模式。某些计划拥有冻结模式frozen schema如 Scan而另一些计划则从子节点推导模式。列Column逻辑计划中的标识符由字段名和关系名组成。物理平面Physical中的组件函数Function一个知道如何从参数物理字段推导自身物理字段、并且知道如何实际对数据执行计算的对象physical_field(PF1, PF2, ...) - PF示例plus(a,b) - PF({a} Plus {b}, d(a.type,b.type), a.nullable | b.nullable)其中d是一个复杂函数当前实现为get_supertype计算逻辑是对两列逐元素求和并返回与两列中较小类型相同的类型length(str) - PF(length({a}), u32, a.nullable)计算逻辑是统计字符串中的字节数计划Plan一棵知道如何推导自身元数据并计算自身的树。规格特别强调物理平面不知道如何推导字段名——字段名完全是逻辑平面的属性因为物理平面并不需要它们。列Column物理计划中一种物理节点类型由字段名和唯一索引unique index组成。周边组件与注册表数据源注册表Data Sources registry源名/关系名 - Schema 及读取数据所需关联属性如文件路径的映射。函数注册表Functions registry函数名 - 逻辑函数 物理函数的映射。物理规划器Physical Planner从逻辑计划推导物理计划的函数plan(LogicalPlan) - PhysicalPlan逻辑优化器Logical Optimizer接受逻辑计划、返回计算相同结果但更高效的优化后的逻辑计划的函数optimize(LogicalPlan) - LogicalPlan物理优化器Physical Optimizer接受物理计划、返回计算相同结果但可能因实际硬件或执行环境不同而不同的物理计划的函数optimize(PhysicalPlan) - PhysicalPlan构建器Builder从已有逻辑计划和额外参数构建新逻辑计划的函数build(logical_plan, params...) - logical_plan七条不变量详解每一条不变量都遵循统一的约束声明 —— 责任方Responsibility—— 验证方式Validation三段式结构。不变量 1逻辑字段和逻辑列中的 (relation, name) 元组唯一逻辑模式中每个逻辑字段的 (relation, name) 元组必须唯一逻辑计划中每个逻辑列的 (relation, name) 元组必须唯一。这条不变量保证了SELECT t1.id, t2.id FROM t1 JOIN t2...能无歧义地在逻辑模式中选中t1.id和t2.id。责任方逻辑构建器和逻辑优化器验证方式在任何创建新模式的逻辑节点scan、projection、aggregation、join 等上构建器和优化器在违反此不变量时必须报错MUST error不变量 2物理模式与数据一致物理计划返回的每个分区中、每个 RecordBatch 中、每个 Array 的内容必须与 RecordBatch 的模式一致RecordBatch 中的每个 Array 必须能够向下转型为 RecordBatch 声明中对应的类型。责任方物理函数必须保证此不变量。这对聚合函数尤其重要——聚合类型可能与计算过程中的中间类型不同例如sum(i32) - i64验证方式由于验证计算代价高昂执行上下文可以CAN验证此不变量物理节点在其输入不满足此不变量时panic!是可以接受的不变量 3物理函数中的物理模式一致物理函数返回的每个 Array 的模式必须与物理函数自身报告的 DataType 匹配。这保证了当物理函数声明它返回某类型如 Int32时用户可以安全地将结果 Array 向下转型为对应类型如Int32Array也可以写入带 nullability 标志的模式格式如 parquet。责任方编写物理函数的开发者具体包括两点推导出的 DataType 必须与它在每种合法输入类型组合分支下构建数组所使用的代码匹配nullability 标志必须与值的构建方式匹配验证方式执行上下文可以CAN验证不变量 4物理模式在规划下不变规划器返回的物理计划推导出的物理模式必须等价于传给规划器的逻辑计划推导出的物理模式plan(logical_plan).schema logical_plan.physical_schema逻辑计划的物理模式定义为逻辑模式中所有逻辑字段去掉关系限定符strip_relation后组成的向量。这保证了物理计划返回的 RecordBatch 模式就是其逻辑计划的物理模式用户可依赖优化后的逻辑计划获知结果物理模式。其推论是每个逻辑函数 - 物理函数的物理模式在规划下也必须不变。责任方物理计划、逻辑计划与规划器的开发者必须为每个三元组逻辑计划、物理计划、转换规则保证此不变量验证方式规划器必须MUST验证——当规划过程中物理函数推导的模式与逻辑函数推导的模式不匹配时必须返回错误不变量 5输出模式等于物理计划模式物理计划输出的每个分区中、每个 RecordBatch 的模式必须等于物理计划的模式physical_plan.evaluate(batch).schema physical_plan.schema结合其他不变量这保证 RecordBatch 的消费者无需知道物理计划的输出模式可以安全地依赖 RecordBatch 自身的模式进行 downcasting 和命名。责任方物理节点验证方式执行上下文可以CAN验证不变量 6逻辑模式在逻辑优化下不变逻辑优化器返回的投影逻辑计划推导出的逻辑模式必须等价于传给规划器的逻辑计划的模式optimize(logical_plan).schema logical_plan.schema这保证了计划可以在不危及后续对逻辑列名称和索引的引用、以及对其模式的假设的前提下被优化。责任方逻辑优化器验证方式逻辑优化器的用户应当SHOULD验证不变量 7物理模式在物理优化下不变物理优化器返回的投影物理计划推导出的物理模式必须与传给规划器的物理计划的模式匹配optimize(physical_plan).schema physical_plan.schema责任方优化器验证方式优化器的用户应当SHOULD验证值得注意的是三条约束力度的梯度规划器在规划时必须验证模式一致性不变量 4逻辑/物理优化器的用户应当验证优化前后模式不变不变量 6、7而关于数据内容的验证不变量 2、3、5由于代价昂贵仅表示为执行上下文可以验证属于可选加固而非强制检查。第二部分输出字段名语义 —— 结果字段名如何从用户查询生成第二份规格 output-field-name-semantic.md 定义了输出 RecordBatch 中字段名应如何根据给定用户查询生成。这些规则同时适用于从 SQL 查询和 DataFrame API 规划的 DataFusion 查询——因此无论用户走哪条 API 路径列名结果都是一致的。七条字段名规则所有裸列字段名不得包含关系/表限定符SELECT t1.id、SELECT id以及df.select_columns([id])都应当得到字段名id所有复合列字段名必须包含关系/表限定符SELECT foo bar应当得到字段名table.foo PLUS table.bar函数名必须转换为小写SELECT AVG(c1)应当得到字段名avg(table.c1)字符串字面量不得用引号或双引号包裹SELECT foo应当得到字段名foo运算符表达式必须用括号包裹SELECT -2应当得到字段名(- 2)运算符与操作数之间必须用空格分隔SELECT 12应当得到字段名(1 2)函数参数必须用逗号,加空格分隔SELECT f(c1,c2)和df.select(vec![f.udf(f)?.call(vec![col(c1), col(c2)])])都应当得到字段名f(table.c1, table.c2)注意规则 1 与规则 2 的对称设计简单引用列时剥掉限定符保证t1.id与id产生相同的列名而一旦列名由表达式运算符、函数参与生成则必须带上限定符以避免不同表的同名列混淆。对比验证与其他数据库系统的行为差异规格以以下测试数据为基础给出完整的跨系统对比CREATE TABLE t1 (id INT, a VARCHAR(5)); INSERT INTO t1 (id, a) VALUES (1, foo); INSERT INTO t1 (id, a) VALUES (2, bar); CREATE TABLE t2 (id INT, b VARCHAR(5)); INSERT INTO t2 (id, b) VALUES (1, hello); INSERT INTO t2 (id, b) VALUES (2, world);场景一投影列Projected columnsSELECT t1.id, a, t2.id, b FROM t1 JOIN t2 ON t1.id t2.idDataFusion Arrow RecordBatch 输出idaidb1foo1hello2bar2worldSpark、MySQL 8、PostgreSQL 13 输出与 DataFusion 一致4 列保留重复列名idSQLite 3 输出不同合并了重复的id列只有 3 列id,a,b场景二函数转换列Function transformed columnsSELECT ABS(t1.id), abs(-id) FROM t1;DataFusion 输出abs(t1.id)和abs((- t1.id))一元负号被括号包裹、参数带限定符、函数名小写Spark 输出abs(id)、abs((- id))参数剥掉了限定符MySQL 8 / SQLite 3ABS(t1.id)、abs(-id)函数名保留原样PostgreSQL 13abs、abs不保留表达式结构直接用函数名abs(t1.id)abs((- t1.id))1122场景三带运算符的函数Function with operatorsSELECT t1.id ABS(id), ABS(id * t1.id) FROM t1;DataFusion 输出t1.id abs(t1.id)、abs(t1.id * t1.id)二元运算左右两侧均带限定符t1.id abs(t1.id)abs(t1.id * t1.id)2144Sparkid abs(id)、abs(id * id)MySQL 8 / SQLitet1.id ABS(id)、ABS(id * t1.id)PostgreSQL?column?与abs场景四投影字面量Project literalsSELECT 1, 25, foo_bar;DataFusion 输出1、(2 5)、foo_bar数字字面量原样、二元表达式加括号、字符串字面量不加引号1(2 5)foo_bar17foo_barSpark 与 DataFusion 一致MySQL 输出1、25、foo_bar运算符不加空格/括号PostgreSQL 全部输出为?column?SQLite 保留引号输出foo_bar这些对比清晰地展示了 DataFusion 字段名语义的定位完整保留表达式结构、统一小写化与规范化空格在可读与可引用之间取得了独特平衡。源码实现佐证schema_name 与 SchemaDisplay输出字段名语义在 datafusion/expr/src/expr.rs 中有直接对应的实现Expr::schema_name()expr.rs#L1603-L1605返回该表达式将产生的列字段名——例如对于投影SELECT expr结果 Arrow Schema 的字段名即由此生成。其 doc 注释明确指出它与Display表示法的微妙差异Expr::Alias只显示别名本身Expr::Cast/Expr::TryCast只显示表达式。底层格式化由SchemaDisplayexpr.rs#L2992-L3051完成二元表达式输出为{} {op} {}运算符两侧带空格Between输出为{} NOT BETWEEN {} AND {}等聚合函数则委托给func.schema_name(params)。qualified_name()expr.rs#L1637-L1647返回表达式的限定符与 schema 名——列与 Alias 保留其 relation其余表达式限定符为 None——这正是裸列去限定符、复合表达式保留限定符规则在代码中的体现。多表达式列表的拼接由schema_name_from_exprsexpr.rs#L3507-L3509实现使用, 分隔符与规格规则 7函数参数用逗号加空格分隔严格对应ExprListDisplayexpr.rs#L3475-L3490支持自定义分隔符。此外datafusion/expr/src/udf.rs、datafusion/expr/src/udaf.rs与datafusion/expr/src/higher_order_function.rs中均定义了schema_name方法说明标量函数、聚合函数与高阶函数都遵循统一的字段名生成契约。这些字段名规则的实际执行结果可通过仓库中的 sqllogictest 用例验证例如 select.slt 与 expr.slt 等测试文件中记录了具体的查询与期望输出列名。不变量与字段名语义的联动两份规格并非孤立不变量 1(relation, name) 元组唯一保证了即使输出中同时存在t1.id和t2.id逻辑列仍可被无歧义引用而不变量 4、6、7 保证在规划与优化过程中模式不发生漂移从而字段名语义在计划优化的各个阶段都保持稳定。字段名规格中的复合表达式必须带限定符规则正是为了避免在投影多个表的同名列时触发歧义——两者在设计上是互相呼应的。如何参与规格的演进如果你在开发或代码评审中发现现有规格有歧义、遗漏或与实际实现不符可以按照 contributor-guide 的流程提出修改建议也可以为新的语义领域例如新的计划节点、新的函数类别起草新规格文档加入docs/source/contributor-guide/specification/目录的 toctree。规格的价值在于让正确行为从口头约定变为书面契约是 DataFusion 这类大规模协作项目维持长期一致性的基础设施。小结不变量规格定义了逻辑/物理平面必须满足的 7 条一致性约束每一条都有明确的责任方构建器、优化器、规划器、物理函数开发者与验证力度MUST/SHOULD/CAN覆盖了从模式唯一性、数据与模式一致、到规划/优化前后模式不变的完整链路字段名语义规格定义了 7 条从查询生成输出字段名的规则并通过与 Spark、MySQL 8、PostgreSQL 13、SQLite 3 的四组对比用例精确定位了 DataFusion 的字段命名行为边界两份规格均可与 expr.rs 中的SchemaDisplay、schema_name、qualified_name等实现相互印证为深入阅读 DataFusion 源码提供了入口。赞分享大数据数据分析后端【免费下载链接】datafusionApache DataFusion SQL Query Engine项目地址https://gitcode.com/gh_mirrors/datafu/datafusion点击查看免费下载相关推荐Apache DataFusion 输出字段命名语义解析:Output Field Name Semantics 规范与源码实现Apache DataFusion 输出字段命名语义解析:Output Field Name Semantics 规范与源码实现 本篇技术文章基于 DataFu大数据数据分析后端Water.css的CSS变量命名规范语义化设计原则Water.css的CSS变量命名规范语义化设计原则 CSS变量CSS Variables是现代前端开发中的重要技术它允许开发者定义可重用的值并在整个样前端3步搞定RTL8188EU无线网卡驱动Linux系统完整解决方案3步搞定RTL8188EU无线网卡驱动Linux系统完整解决方案 RTL8188EU开源驱动项目为Linux用户提供了解决Realtek RTL8188EU无驱动开发嵌入式网络上一篇NestJS Swagger 使用教程下一篇Wave-Share基于WebRTC的无服务器、点对点本地文件共享实验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考