驻留数据集 IR
PurRDF 的驻留载体共享一种按值驻留的 RDF 1.2 项表示,由隔离的 purrdf-core 内核拥有。
项只驻留一次
每个项——IRI、空节点、字面量、三元组项——都只驻留一次,并通过一个可复制的 TermId(经过 niche 优化的 NonZeroU32)寻址。文本载荷使用字符串区块(arena);三元组项保留其组成部分的 ID。四元组是由四个 TermId 组成的行。这使得项的相等比较只需一次整数比较,使四元组保持固定的较小尺寸,也意味着一个出现在一百万个四元组中的项,其字节恰好只付一次代价。
热点映射使用固定密钥的 FixedHasher——确定性哈希是字节确定性纪律的一部分,而不只是出于速度的选择。
构建器 → 冻结
IR 具有严格的两阶段生命周期:
#![allow(unused)]
fn main() {
use purrdf::{RdfDatasetBuilder, RdfLiteral};
// 项只驻留一次;四元组是由可复制的 TermId 组成的行。
let mut b = RdfDatasetBuilder::new();
let cat = b.intern_iri("https://example.org/cat")?;
let says = b.intern_iri("https://example.org/says")?;
let meow = b.intern_literal(RdfLiteral::simple("meow"))?;
b.push_quad(cat, says, meow, None)?;
// 冻结为各引擎在其上求值的、不可变且带索引的数据集。
let ds = b.freeze().expect("well-formed dataset");
assert_eq!(ds.quad_count(), 1);
Ok::<(), Box<dyn std::error::Error>>(())
}
RdfDatasetBuilder 是可变的摄入阶段:驻留项、推入四元组、附加具体化节点与注解。freeze() 验证结构并产出一个不可变的 RdfDataset:四元组行存放在 Box<[QuadRow]> 表中,并带有惰性构建的序数置换索引(每个轴每个四元组约 4 字节)。SPARQL、SHACL、ShEx 与蕴涵读取的都是这个冻结的数据集,经由零分配的 DatasetView trait。
冻结也让并发变得简单:冻结的数据集是不可变的,因此可以在多个线程之间共享并读取(C ABI 正是把它作为一个 Send + Sync 句柄提供)。
显式无图名载体
当数据没有命名图时,选择 TripleDatasetBuilder。其断言行以 12 字节存储主语、谓词和宾语;具体化绑定占 8 字节,注释占 12 字节。它与 RdfDatasetBuilder 共享项驻留和验证,但不累积四元组行或图表。
TripleDataset::try_from 从仅含默认图的四元组数据集中移出原有的项所有者。对于任何命名行或命名图声明(包括空图),它都会以 TripleReadError::NamedGraphsUnsupported 拒绝转换。
通过 TripleDataset::read() 打开可失败的视图。即使载体为空或图名不存在,命名图查询请求也会被拒绝。该读取操作的所有子读取都保留相同的首个失败;在未改变的载体上重新打开读取会启动独立操作。引擎的普通默认图查询路径使用紧凑载体的 SPO、POS 和 OSP 顺序。
进行规则物化时,purrdf::shapes::srl::infer_triples 接受该读取视图并返回 Inference<TripleDataset>。其 Datalog 行有三个位置,分区键只含谓词。普通四元组 srl::infer API 保留原有返回类型。文本索引显式选择 TextIndexConfig::for_triples;文档键变为主语/语言,分区键变为语言,并采用独立的无图名身份。
写时复制修改
「不可变」并不意味着「静态」。MutableDataset 在冻结基底之上的写时复制增量中累积编辑。其 snapshot_view() 共享未改变的基底行、项和索引。其 freeze() 则把有效数据集压实为一个新的冻结数据集,重新驻留仍然存在的项并重建稠密 ID。SPARQL UPDATE 与 C ABI 的可变 PurrdfGraph 句柄都使用这一修改层。
内核中还有什么
在 IR 本身之外,purrdf-core 还拥有:
DatasetView——每个引擎都在其上求值的静态读取 trait。- 结构化诊断——带源位置的类型化
RdfDiagnostic(刻意不涉及 SARIF;SARIF 边界位于purrdf-validate)。 - 规范化(在 RDF 1.1 子集上为 RDFC-1.0;在具体化节点与注解上为
purrdf-rdfc12profile)、数据集差异比较以及同构判定——参见规范化与差异比较。 - 存储与引擎扩展点——由兄弟 crate 中的适配器实现的、窄小的解析器入口、序列化器出口与
SparqlEnginetrait。 - 溯源与损失台账——一个通用的溯源旁挂结构以及机器可读的 RDF↔GTS 损失矩阵,外加原生的 FnO 与 SSSOM 编解码器(参见切片、映射与溯源)。
文本编解码器_不_在内核中——解析与序列化位于上一层的 purrdf-rdf 中。这种拆分让内核保持小巧,并让其不变量能在 crate 边界上强制执行:不含 PyO3(一项卫生检查会对依赖树进行断言),wasm32-clean(可干净构建到该目标),且 IR 层不涉及文件 IO。
为何如此设计
布局是通过测量而非断言选定的:基准 crates/rdf-core/benches/ir_layout.rs 在分配次数、内存高水位以及端到端延迟上比较结构体数组、数组结构体与谓词邻接三种布局——最终发布的是胜出的那一种。参见性能。