SPARQL 1.2 RL

W3C 工作草案

关于本文档的更多详细信息
此版本:
https://www.w3.org/TR/2026/WD-sparql12-rl-20261031/
最新发布版本:
https://www.w3.org/TR/sparql12-rl/
最新编辑者草案:
https://w3c.github.io/data-shapes/sparql12-rl/
历史记录:
https://www.w3.org/standards/history/sparql12-rl/
提交历史记录
测试套件:
https://github.com/w3c/data-shapes/tree/gh-pages/shacl12-test-suite/tests/sparql-rl
编辑者:
Robert David (Ontotext)
Robert David (GraphWise)
(KurrawongAI)
Andy Seaborne (Apache Software Foundation)
Simon Steyskal (Siemens AG)
反馈:
GitHub w3c/data-shapes (拉取请求, 新建议题, 未解决的议题)

摘要

本文档定义了 SPARQL-RL,一种 面向 RDF 的 Datalog 风格规则语言。

SPARQL-RL 通过针对输入 RDF 图对一组声明式 规则进行求值来生成新的 RDF 数据。规则使用一种 类似 SPARQL 的文本语法编写。

本规范由 W3C Data Shapes 工作组发布。

本文档状态

本节描述本文档 发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 标准和草案 索引中找到。

本文档由 Data Shapes 工作 组作为 工作草案,按照 推荐标准 轨道发布。

作为 工作草案发布并不意味着 获得 W3C 及其成员的认可。

这是一份草案文档,可能随时由其他 文档更新、替换或废止。不应以 进行中的工作以外的形式引用本文档。 此即将发布的推荐标准的未来更新可能会纳入 新特性。

本文档由一个 根据 W3C 专利 政策运作的工作组制定。 W3C 维护着一份 与该工作组交付成果相关的任何专利 披露的公开列表, 该页面还包含 披露专利的说明。任何实际 知晓某项专利,并且认为该专利包含 必要权利要求 的个人,都必须按照 W3C 专利政策第 6 节的规定披露该信息。

本文档受 2025 年 8 月 18 日 W3C 流程文档约束。

1. 引言

本文档介绍 SPARQL-RL。它是一种通过声明式规则 从现有 RDF 数据派生新 RDF 三元组的机制。 本文档定义了基于规则的推理的语法和语义。

SPARQL-RL 的实现提供以下一种或两种操作: 推理和查询。 推理操作将规则应用于给定的 基础图,并生成一个推理图,其中包含 由规则求值派生且未出现在基础图中的 RDF 三元组。 是否将推理图与基础图合并是可选的,并 由用户决定。 查询操作确定给定的 目标模式是否可以以及如何使用规则 从基础图派生出来。

SPARQL-RL 允许创建新的 RDF 术语,包括空白节点, 这些术语可以用于规则头中的三元组模板。

SPARQL-RL は失敗による否定をサポートしており、これにより 規則が評価される順序に応じて異なる推論グラフが 生成される可能性がある。これを防ぐため、規則の評価には 層化という手法を用いる。この手法は規則間の順序を定め、 常に同じ推論グラフが生成されることを保証する。

SPARQL-RL はグループ化と集約を提供し、その際、 集約関数の結果を規則の本体内の変数に代入する。

当上下文明确时,SPARQL-RL 可以称为 SRL。

1.1 术语

以下规范提供了本文档中使用的 基础术语:

1.2 文档约定

本文档中的 RDF 数据示例使用 RDF 1.2 Turtle [RDF12-TURTLE]。

在本文档中,使用以下命名空间前缀绑定:

前缀 命名空间
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
xsd: http://www.w3.org/2001/XMLSchema#
: http://example/

在整个文档中,带颜色标记的框包含 SPARQL-RL 语法中的规则 或 Turtle 语法中的 RDF 图。

# 此框表示规则
# 此框表示输入数据
# 此框表示推理数据

2. 一致性

除标记为非规范性的章节外,本规范中的所有编写指南、图表、示例和注释均为非规范性内容。本规范中的其他所有内容均为规范性内容。

本文档中的关键字 MAY、MUST 和 MUST NOT 仅当它们以全大写形式出现时(如上所示),才应按照 BCP 14 [RFC2119] [RFC8174] 中的规定进行解释。

本规范为以下内容定义了一致性标准:

符合规范的 SPARQL-RL 文档 是一个采用 UTF-8 编码 [RFC3629] 的 RDF 字符串, 并且符合 7.6 语法 中定义的语法和附加约束。

注

本规范未定义 SPARQL-RL 处理器 如何处理不符合规范的 SPARQL-RL 文档。

3. SPARQL-RL 概述

本节为非规范性的。

给定一个 基础图和一个 规则集,SPARQL-RL 会推理出新的三元组。 求值的输出是一个 推理图,其中包含派生出的 未出现在基础图中的三元组。

每个 规则都有一个模式,称为主体,以及一个结果 模板,称为头部。 执行规则时,会查找主体中变量的值 使主体与合并后的基础图以及 到当前执行阶段为止的所有推理三元组相匹配。 然后使用这些值实例化 规则头中的三元组模板,以生成新的推理三元组。

规则会一直执行,直到不再推理出新的三元组, 并且随着新的推理三元组 可用,规则可能会执行多次。

SPARQL-RL 的执行定义确保规则执行顺序 在创建新的 RDF 术语时不会导致不同结果, 包括新的空白节点,或在测试某个 模式是否不存在时。换言之,都会生成相同的推理图, 无论规则执行顺序如何。

SPARQL-RL 具有受 SPARQL 1.2 查询语言启发的易于人类使用的语法。 规则集求值包含与 SPARQL 类似的元素,但在 细节上有所不同,以确保生成相同的推理图, 无论规则执行顺序如何。

本节中的示例描述软件组件及其 依赖关系:一个前端调用应用服务器,而 应用服务器查询一个存在已知 漏洞的数据库。

3.1 三元组模式

一个 三元组模式 与三元组数据进行匹配,以便为 变量赋值。该模式具有 三个元素,每个元素都是一个 RDF 术语、一个变量, 或者是一个 三元组术语 的包含变量的模式。

在第一个示例中,基础图和规则集中包含以下数据:

将上述规则应用于数据,将得出以下结论: :frontend 依赖于 :app,而 :app 依赖于 :db,无论依赖关系的类型如何。

随后,我们可以通过添加一条依赖于 其他规则所生成的 :dependsOn 三元组的规则来推导 :exposedTo 关系 (另见下文的 4.3 规则 依赖):

3.2 递归

上一节中的 :exposedTo 规则仅涉及直接 依赖::frontend 并不直接依赖于 :db,因此 不会为 :frontend 推断出 :exposedTo 三元组。 为了沿任意长度的依赖链传播暴露关系,我们 将该规则替换为两条规则:组件暴露于其自身具有的 漏洞,并且组件也暴露于任何 其直接依赖项所暴露的漏洞:

与前一个示例相比,这里增加了 :db :exposedTo :vuln1 — 数据库暴露于其 自身的漏洞 — 以及 :frontend :exposedTo :vuln1: 暴露关系通过依赖链到达 :frontend ,无论该依赖链有多长。

最后一条规则是一条递归规则:规则体 依赖于规则头。

3.3 过滤

我们可以在规则体中使用表达式来限制变量的值 ,这些变量用于规则体的匹配。 例如,给定每个漏洞的严重程度, 我们可以为暴露于严重 漏洞的组件赋予一个状态:

FILTER 对表达式求值,并保留当前的 变量绑定集合(如果表达式求值为真),而它会丢弃 当前的变量绑定集合(如果表达式求值为假)。 这与 FILTER 这一 SPARQL 操作相同 ,并且 SPARQL-RL 提供了许多与 SPARQL 相同的函数和运算符。

3.4 否定

否定用于指定一个不得匹配的模式。 这称为“失败即否定”。

为了对否定元素求值,规则集求值算法 确保所有可能生成与该模式匹配的三元组的规则 都已执行完毕,该模式位于否定元素中。这称为 分层,并确保否定所依据的是 所有相关的可能三元组,无论它们来自数据还是 来自其他规则。包含否定的规则 被称为依赖于生成这些三元组的规则 (参见下文的 4.3 规则 依赖)。

3.5 赋值与创建 RDF 术语

赋值允许规则将表达式的结果赋给一个变量 ,该变量位于规则体中。这可用于创建新的 RDF 术语,其依据 是数据。

空白节点可以用于规则头,并且每个空白节点都会生成一个全新的空白节点 ,每当规则求值生成三元组时都是如此。

涉及赋值的规则、通过对规则头求值来创建空白节点的规则、涉及聚合的规则,以及其模板涉及包含变量的三元组术语的规则,都是仅运行一次的规则。此类规则在所有可能生成其所依赖数据的规则之后运行,并且在任何依赖于其所生成数据的规则之前运行。

此条件确保此类规则不会循环回到自身,从而导致创建无界数量的 RDF 术语,或导致三元组术语的无界嵌套。

注

一个常量 RDF 术语 位于一个 规则头 中,并不会 使规则成为 仅运行一次的规则,即使该术语未出现在 基础图 中:该规则每次都会贡献相同的术语,当它被 求值时,因此它只能添加有界数量的术语。

如果对某个 赋值 中的表达式求值导致错误, 则当前解映射会被该 赋值 拒绝。

3.6 分组与聚合

分组与聚合允许规则将模式的解归入各个分组,然后为每个分组计算汇总值。聚合值被赋给变量,这些变量随后可用于规则头。包含分组元素的规则是仅运行一次的规则。

分组元素处理前面规则元素产生的每个解序列时,只取分组键的绑定,求值分组模式,然后使用分组键对解映射进行分组。

输出是一个解序列,每个分组对应一个解映射:即用分组键的值和聚合变量的值扩展输入解映射。

例如,我们可以统计每个组件暴露于多少个漏洞:

对前面的三元组模式找到的每个组件,都匹配分组模式 ?x :exposedTo ?v。解按照分组键(此处为 ?x)归入各个分组。对于每个分组,将 COUNT(*)(该分组中的解的数量)赋给聚合变量 ?n。

:logger 未暴露于任何漏洞,因此分组模式对它没有解,也就没有分组,因而不会为 :logger 推断出 :vulnerabilityCount 三元组。

仅在分组模式内部绑定的变量(如 ?v)不在分组元素之后的作用域内:只有分组键和聚合变量会添加到解映射中,因此 ?v 不能用于后续的规则元素或规则头。

3.7 导入规则集

一个 SPARQL-RL 规则集 可以纳入其他规则集,方式是 将其 URL 包含在该规则集的 规则集导入 中。 这允许将规则组织成可在规则集之间共享的库。

规则集的 IMPORTS 语句会先被处理,然后才会对 规则集中的任何规则求值。在导入步骤中,如果导入的规则集 自身也有导入项,则这些导入项也会被递归处理。 在处理规则集期间遍历 IMPORTS 语句时,可能 遇到循环导入。一个规则集只会被导入一次; 导入语句图中的环不会导致无限循环。

对导入规则集的支持是可选的,对于 SPARQL-RL 处理器 而言。 参见 4.5 处理 导入 以了解更多详细信息。

3.8 数据块

数据块直接为规则集求值提供 RDF 三元组。 数据块中的三元组会被添加到推断图中,并可用于 规则体中的匹配。

例如,规则集可以附带已知事实,这些事实是直接 断言的,而非从数据中推导出的:

注

数据块等价于规则体为空的规则: 其三元组是推断图的一部分,无需对任何规则 求值。

3.9 匹配基础数据

在 规则集求值 期间,可能 需要仅对原始的 基础图 进行模式匹配, 而不对任何推断出的三元组进行匹配。

设置默认值就是一个例子。 规则必须测试基础图是否已包含某个值,如果没有, 则该规则可以计算一个默认值。

使用 NOT DATA 允许规则体的其他部分匹配推断出的三元组。 也可以执行所有 规则体 匹配,针对 基础图,方法是 使用 WHERE DATA。 此类规则便仅依赖于来自基础图的匹配。

注

本节中的匹配针对的是 基础图。 它不包括由 规则集求值 从 规则集 DATA 块添加的三元组 (3.8 数据块)。

3.10 规则集求值

规则被组织成 规则集。 一个规则集和一个数据图(即 基础图)是求值的输入。 输出是一个图,称为 推断图, 它是求值过程生成的三元组的集合 ,这些三元组未出现在 基础图 中。

在求值期间,基于一条规则推断出的三元组 可用于其他规则中的匹配。 规则集求值持续进行,直到 无法再推断出更多三元组 ,这些推断以 规则集 和 基础图 为输入。

求值首先执行以下步骤来 准备规则集 ,然后才对规则本身求值:

  1. 在支持导入的情况下,解析输入规则集的导入项,以形成一个 规则集,其中包含所有 导入的规则集中的全部规则。 如果任何导入项不可被接受,处理器会发出错误信号 ,由处理器判定(4.5 处理导入)。
  2. 检查每条规则是否都是 良构规则。
  3. 计算 依赖图。
  4. 一个 分层 被计算出来 (4.4 分层) ,使得 否定元素 和 仅运行一次的规则 产生一致的、 可预测的结果。分层 涉及检查 依赖关系 ,这些关系存在于规则之间 (4.3.1 依赖 图)。

一旦一个 规则集 准备完毕,求值便继续进行 ,依次取出每个 层,按分层中的顺序, 对该层中的规则求值直至完成, 然后继续处理下一层。 当执行规则推断出三元组时,这些三元组会立即 可供其他规则在执行时使用。

4. SPARQL-RL 抽象语法

SPARQL-RL 抽象语法是 SPARQL-RL 的逻辑结构。 它用于定义 SPARQL-RL 的执行算法。

4.1 抽象语法的元素

变量
一个 变量 表示一个可能的 RDF 项,位于三元组模式中。 变量也用于 表达式。
表达式
一个 表达式 是一个 RDF 项、一个 变量, 或者是将函数或函数形式应用于实参的结果, 其中每个实参本身都是一个 表达式。 表达式根据一个 解映射 进行求值,得到 一个 RDF 项 作为结果,或者求值导致一个 错误。 表达式 兼容 SPARQL 表达式。
数据块
一个 数据块 是一个三元组集合。 这些三元组作为附加事实添加到推理图中,并 参与推理过程。
三元组模式
一个 三元组模式 是一个三元组,其中每个元素都是 一个 变量、一个 RDF 项,或者是一个模式,用于表示 三元组项,其中包含 变量。 该元组的第二个元素是一个 IRI 或一个 变量。 三元组模式 出现在 主体 中,该主体属于一个 规则 并用于匹配 RDF 三元组。
三元组模板

一个 三元组模板 是一个三元组,其中每个元素都是 一个 变量、一个 RDF 项,或者是一个模板,用于表示 三元组项,其中包含 变量。 该元组的第二个元素是一个 IRI 或一个 变量。 三元组模板 出现在 头部 中,该头部属于一个 规则,并 用于生成 RDF 三元组。

注

一个模式或模板,若用于表示 三元组项,且其中没有 变量 出现,则它就是一个 三元组项,因此也是 一个 RDF 项。

三元组模式元素
一个 三元组模式元素 是一个 三元组模式 其出现时的身份为一个 规则元素。
过滤元素
一个 过滤元素 是一个 表达式 其出现时的身份为一个 规则元素。 它用于限制变量的取值 在模式匹配过程中。
否定元素
一个 否定元素 是一个 规则元素。 它有一个 否定元素主体 由以下元素的序列组成:三元组模式元素 和 过滤元素。 一个 否定元素 有 一个 标志, .evaldata, 用于指示使用哪个图来匹配 该 否定元素主体。
赋值 元素
一个 赋值元素 是一个 规则元素,其形式是 一个有序对,组成项 为一个 变量,称为 赋值变量, 以及一个 表达式,称为 赋值表达式。
分组元素
一个 分组元素 是一个 规则元素 带有一组变量,称为 分组键, 一个 分组 模式,以及一组 聚合赋值。 每个 聚合赋值 是一个有序对,其组成项为 一个 变量,称为 聚合变量, 以及一个 聚合函数。 该 分组模式 的组成 是以下元素的序列:三元组模式元素、过滤元素和赋值元素。
规则元素
一个 规则元素 是以下任意一种: 三元组模式元素、 过滤元素、 否定元素、 赋值元素,或 分组元素。
规则头
一个 规则头 是一个序列,其元素为 三元组 模板。
规则体
一个 规则体 是一个序列,其元素为 规则元素; 也就是说,每个序列元素都是以下之一: 一个 三元组模式元素、 一个 过滤元素、 一个 否定元素、 一个 赋值元素,或 一个 分组元素。
规则集导入
规则集导入 (通常简称 "导入") 是 URL 的集合 这些 URL 来自 IMPORTS 语句 用于指定求值过程中将包含的其他规则集。
规则
一个 规则 包含一个 规则头 (通常简称 "头部"), 一个 规则体 (通常简称 "主体"), 以及 一个标志 .evaldata 用于指示使用哪个 图来匹配 三元组模式,这些模式位于主体中。 可以为规则指定一个 URI 或空白节点,以帮助标识它。
单次运行规则

一个 单次运行规则 是一个恰好运行一次的规则,运行于 规则集求值过程中的某个特定时刻。 一个 规则 属于 单次运行 规则,如果以下任一条件成立:

一般规则
一个 一般规则 是一个不属于 单次运行规则 的规则。 一般规则在规则集求值过程中可能运行多次。
规则集
一个 规则集 包括一个集合,包含零个或多个 规则, 一个集合,包含零个或多个 数据块, 以及一个集合,包含零个或多个 规则集导入。 一个 已解析 规则集 是一个 规则集 其中没有导入。 一个 已解析规则集 从另一个 规则集 创建 通过应用 导入过程。
基础图
一个 基础图 是一个 RDF 图,作为输入提供给 求值过程。
推理 图
一个 推理图 是一个 RDF 图,生成于 一次 规则集求值。 它包含所有推导出的三元组,这些三元组不存在于 基础图 且通过应用以下规则集推导得出: 规则集,应用目标为 基础图。
规则集 求值
一次 规则集求值 是应用一个 规则集 的过程 应用目标为一个 基础图,以生成一个 推理 图。 一次 规则 求值 是对规则求值一次的过程。 对规则求值会生成所有由以下部分给出的三元组:规则头 其生成依据是对以下部分的求值:规则体,无论一个 三元组是位于 基础图 中、已在以下过程中推导得出:规则集求值, 还是新推导出的三元组。 在 规则集求值 过程中,一个规则的求值次数可能多于 一次。

SPARQL-RL 提供两个操作,推断 和 查询。

推断
推断 是将一个 规则集 应用于给定的 基础图 并生成一个 推断图 的操作,该图包含推断出的 三元组。它应用完整的 规则集求值。
查询

查询 是确定给定模式是否 可从一个 基础图 推导出的操作,使用该 规则集,并 返回一个 解序列,其中每个 解映射 为变量给出 RDF 术语。

查询 的效果相当于执行一次 推断 操作, 然后将目标模式与合并后的 基础图 和 推断 图 进行匹配。

该 查询 操作可以不对所有规则求值;而是,它 可以只对回答查询所必需的规则求值 ,以满足查询目标。

SPARQL-RL 处理器
一个 SPARQL-RL 处理器 是一个实现了 SPARQL-RL 规范的系统,能够对 规则集 求值,并提供一个或两个 操作,即 推断 和 查询。

在一个 三元组模式 或一个 三元组 模板 中, 元组的位置 1 非正式地称为主语, 位置 2 非正式地称为谓语,而 位置 3 非正式地称为宾语。

注

一个序列中的元素(该序列由 规则元素 组成)从 1 开始编号。

以下记法用于规则集和规则的各个组成部分。

组成部分记法
组成部分 用途
ruleset.rules 规则,属于一个 规则集。
ruleset.data 该 RDF 图 由以下各项的并集形成:数据块,这些数据块位于规则集中。
ruleset.imports 一组 导入,属于一个 规则集。
rule.head 这些 三元组模板 属于该 规则头。
rule.body 这些 规则元素 属于该 规则。
rule.evaldata 一个布尔标志;如果为 false,则 规则体 的 匹配 针对该 基础图。
rule.id 规则的标识符,是一个 空白节点 或 IRI。
filter.expr 该 表达式 属于一个 过滤 元素。
assign.var 该 赋值变量 属于该 赋值元素。
assign.expr 该 赋值表达式。
negation.inner 该 否定元素主体 属于一个 否定元素。
negation.evaldata 一个布尔标志;如果为 false,则 negation.inner 的匹配 针对该 基础图。
group.keys 一组 分组键,属于一个 分组 元素。
group.pattern 该 分组模式 属于一个 分组元素。
group.aggregations 这些 聚合 赋值 属于一个 分组元素。
aggregation.variable 该 聚合变量 属于一个 聚合赋值。
aggregation.function 该 聚合函数 属于一个 聚合赋值。

4.2 良构性条件

良构性 是一组关于抽象语法的条件,适用于 一个 规则集。这些条件共同确保 一个 变量,若位于 头部(属于某个规则),则其值已定义在 主体(属于该规则)中; 每个变量,若位于一个 过滤元素 或 赋值表达式 中,则在求值时具有一个值; 并且每个 赋值变量 和 聚合变量 在规则中都是新的,且此前未在规则体中使用过。

令 S 为一个序列,其元素为 规则元素。

令 elti 为 S 的第 i 个元素, 并令 N 为该序列的长度。

令 V0 为一组变量,称为初始 变量,所属序列为 S。

将 vars(S, i, V0) 定义为一个变量集合 这些变量在序列 S 的第 i 个元素之后处于作用域内, 给定初始变量 V0。vars(S, i, V0) 的定义采用 归纳方式:

  • 令 vars(S, 0, V0) 为 V0。
  • 令 vars(S, i, V0),对于从 1 到 N 的每个 i,取值如下
    • 如果 elti 是一个 三元组模式元素 且 X 是变量的集合,这些变量出现在 三元组模式 元素 中 则
      • vars(S, i, V0) 是集合 X ∪ vars(S, i-1, V0)。
    • 如果 elti 是一个 赋值元素 其 赋值变量 为 avar,则
      • vars(S, i, V0) 是集合 { avar } ∪ vars(S, i-1, V0)。
    • 如果 elti 是一个 分组元素,GRP, 其中集合 aggVars 是以下各项的集合: 聚合变量,属于 GRP 则
      • vars(S, i, V0) 是集合 vars(S, i-1, V0) ∪ GRP.keys ∪ aggVars。
    • 否则,
      • vars(S, i, V0) 等于集合 vars(S, i-1, V0)。

将 vars(S, V0),即 S 中处于作用域内的变量集合,定义 为 vars(S, N, V0),即处于作用域内的变量集合 在该序列的最后一个元素之后。

一个序列,其元素为 规则元素,记为 S, 是一个 良构序列, 给定一组变量 V0, 如果以下条件对每个 i 均成立, 其范围为 1 到 N:

一个 规则 是一个 良构 规则 如果以下元素组成的序列:规则元素,属于 规则体, 是一个 良构序列,且起始时 V0 为空集, 并且每个变量,若位于一个 三元组模板 中,该模板属于 规则头, 则是 vars(S, V0) 的元素。

一个 规则集 是一个 良构规则集,当且仅当所有 规则(属于该规则集)都是 良构规则。

4.3 规则依赖

规则 R1 依赖于规则 R2,如果第二个规则的输出 影响第一个规则主体的求值。也就是说,R2 的头部 有一个 三元组模板,能够生成一个三元组,该三元组可匹配 一个 三元组模式,位于 R1 的主体中, 该模式可以作为一个 三元组模式元素,也可以位于一个 否定元素 或一个 分组模式 内。

依赖有两种: 封闭依赖 和 开放 依赖。 一个封闭依赖确保规则 R2 在规则 R1 执行之前已生成其所有可能的输出。 如果规则依赖不是封闭的,则它是一个开放依赖。 它允许第一条规则 R1 执行 ,同时规则 R2 仍可能再次运行以生成更多三元组 ,这些三元组随后可能使 R1 使用来自 R2 的新三元组重新求值。

标志 .evaldata 设置为 false 的规则使用 基础图 进行匹配,并且该规则没有依赖。

在第一个示例中,第一条规则具有一个开放依赖 ,依赖于第二条规则。

在第二个示例中,第一条规则具有一个封闭依赖 ,依赖于第二条规则:三元组模式 ?x :status :criticallyExposed 出现在一个 否定元素 内部,该元素属于第一条规则,并且该模式 匹配一个 三元组模板,该模板位于第二条规则的规则头中。 第一条规则不能求值,直到第二条规则 已生成其所有可能的输出。

三元组模式匹配

一个 三元组模式 匹配一个 三元组模板,如果 该三元组模板能够 生成 一个三元组,该三元组 匹配 该三元组模式。

三元组模式依赖

一个 三元组模式 依赖于 一个 三元组模板 ,如果该 三元组模式 能够 匹配 该 三元组模板。

规则依赖

规则 R1 依赖 于 规则 R2,如果 R1.evaldata 为 true,并且任一 三元组 模式(位于 R1 的主体中), 无论其作为一个 三元组模式元素, 还是位于一个 否定元素 neg 中,其中 neg.evaldata 为 true, 或者位于一个 分组模式 中, 依赖于一个 三元组模板 位于 R2 的头部。

封闭依赖

一个 规则依赖,由规则 R1 依赖于规则 R2,是一个 封闭依赖 ,如果以下任一条件成立:

开放依赖

一个 规则依赖,由规则 R1 依赖于规则 R2, 是一个 开放依赖,如果该依赖不是一个 封闭依赖。

如果存在三元组模板变量的值,使得在模板中用值替换变量 得到一个三元组 T2,其中 T2 与 T1 相等,则一个三元组模板可以生成一个 RDF 三元组 T1。

类似地,如果存在一组变量值,使得将三元组模式中的变量替换为这些值后,匹配三元组 T1, 则该变量值必须使 三元组模式中的变量被替换,包括 将空白节点视为 变量, 从而得到三元组 T2,其中 T2 与 T1 相等 。

如果一个变量在三元组 模板 或三元组模式中使用多次,则使用相同的 RDF 术语 作为替换项。

在三元组模式中用 RDF 术语替换变量 包括替换三元组术语内部的变量。

4.3.1 依赖图

规则之间的依赖表示为一个有向图, 称为依赖图。 图的顶点是规则集中的规则,而边 根据依赖是开放依赖还是闭合依赖, 分别标记为开放或闭合。

依赖图
一个规则集的依赖图是一个有向图,其中每个 顶点都是该规则集中的一个规则,并且如果规则 R1 依赖于规则 R2,则存在一条从规则 R1 到规则 R2 的边。 该边根据依赖 是开放依赖还是闭合依赖, 分别标记为开放 或闭合。
传递规则依赖
如果在依赖图中存在一条从 R1 到 R2 的路径, 则规则 R1 对规则 R2 具有传递依赖。
递归规则依赖

如果依赖图中存在一条涉及 R 的 循环路径,则规则 R 具有递归依赖。

注

依赖图不受基础图影响。

4.3.2 依赖图算法

以下算法给出一种 构造以下对象的可行方法:依赖 图,来源为一个 规则集。符合性取决于是否生成一个依赖图 该图满足依赖图的定义, 而不取决于是否使用此过程。

define mergeLabel(oldLabel, newLabel):
    # 封闭依赖覆盖开放依赖。
    if oldLabel == "open" and newLabel == "open":
        the result is "open"
    else:
        the result is "closed"
    endif
enddefine

# 输出 -- 以规则为顶点且边带有标签的依赖图。
define buildDependencyGraph(ruleSet):
    # edgeLabelMap 将 (R1, R2) 映射到 "open" 或 "closed"
    let edgeLabelMap be a map from pair (rule, rule) to label

    foreach rule R1 in ruleSet.rules:
        let bodyDependencies = {}

        if R1.evaldata then:
            # 将规则中的每个三元组模式 TP 分类为需要 "open" 或 "closed"
            # 取决于它是否位于否定元素或分组模式中。
            foreach rule element RBE in R1.body:
                if RBE is a triple pattern element of triple pattern TP:
                    let item be a pair (TP, "open")
                    add item to bodyDependencies
                else if RBE is a filter element:
                    # 不执行任何操作
                else if RBE is a negation element:
                    if RBE.evaldata then:
                        foreach triple pattern TP in RBE.inner:
                            let item be a pair (TP, "closed")
                            add item to bodyDependencies
                        endfor
                    endif
                else if RBE is an assignment element:
                    # 不执行任何操作
                else if RBE is a group element:
                    foreach triple pattern TP in RBE.pattern:
                        let item be a pair (TP, "closed")
                        add item to bodyDependencies
                    endfor
                endif
            endfor
        endif

        foreach pair (triple pattern TP, depLabel) in bodyDependencies:
            if R1 is a run-once rule:
                set depLabel to "closed"
            endif
            # 查找此三元组模式元素或否定元素的依赖关系。
            foreach rule R2 in ruleSet.rules:
                foreach triple template TT in R2.head:
                    if triple pattern TP matches triple template TT:
                        let key = (R1, R2)
                        if edgeLabelMap contains key:
                            let oldLabel = edgeLabelMap.get(key)
                            let merged = mergeLabel(oldLabel, depLabel)
                            edgeLabelMap.set(key, merged)
                        else:
                            edgeLabelMap.set(key, depLabel)
                        endif
                    endif
                endfor
            endfor
        endfor
    endfor

    let DP = { }
    foreach entry ((R1, R2), label) in edgeLabelMap:
        add an edge (R1 -> R2) labeled with label to DP
    endfor

    the result is DP
enddefine

4.4 分层

分层 是划分一个 规则集 的过程 将其划分为一个有序序列,其元素为 分层层级(也称 为“strata”,单数为“stratum”)。规则若位于较低的 层,则其 求值先于位于较高 层 中的规则。

分层 对以下各项之间的依赖关系施加约束:规则 以确保 否定元素、分组元素, 以及 单次运行规则 仅依赖 计算结果,这些结果使用较早(较低)的 层 和 基础图 计算得出。 这保证了一个唯一、定义明确且有限的 求值结果,其求值对象为一个 规则集,求值基于给定的 基础图。

注

分层过程也可用于作出其他求值 决策。本文档描述了必要条件,用以实现 一致的求值,并给出一种可行的方式来形成 分层。实现需要满足条件, 即此处描述的条件,以获得兼容的行为,但并不 要求按所呈现的形式实现此算法。

分层层级

一个 分层层级 SL 是一个有序对 由两个不相交的集合构成,其中元素为 规则 (SL.once, SL.general)。 SL.once 恰好是这些 单次运行 规则,它们属于该层级; 这些规则中的每一个都被求值 恰好一次,发生在以下对象的求值开始时:分层层级。 SL.general 是该层级中其余规则的集合, 这些规则会被反复求值,直到不再推导出新的三元组。

分层

一个 分层,属于一个 规则集,是一个序列,其元素为 分层层级。 每个规则,若位于一个 规则集 中,均恰好出现在某一个集合中,该集合属于某一个 分层层级。

这些 分层层级,属于一个 分层,其编号 从零开始。层号,对于一个 规则,指的是编号 该编号属于一个 分层层级,其中包含该规则。 规则若具有较小的 层号,则位于一个 较低 的层; 规则若具有较大的 层号,则位于一个 较高 的层。

对于每条从规则 R1 指向规则 R2 的边,它位于 依赖图 中,该图属于 规则集:

  • 如果该边的标签为 封闭,则 层 号(所属规则为 R1)严格大于 层 号(所属规则为 R2)。
  • 如果该边的标签为 开放,则 层 号(所属规则为 R1)大于或等于 层 号(所属规则为 R2)。
注

一个 规则集 可以有不止一个 分层。 实现可以使用任意一个 分层,属于该 规则集。

4.4.1 分层条件

分层 有定义的前提是以下 条件得到 满足。如果一个 规则集 不满足此条件,那么本 规范不定义求值结果,其求值对象为 这样的一个 规则集。

分层条件
该 分层条件 要求 不存在这样的 递归依赖: 涉及一个 封闭依赖, 位于 依赖图 中,该图对应一个 规则集。
注

该 分层条件 恰好就是 以下对象的存在条件: 分层 的存在:一个 分层,属于一个 规则集,能够 形成,当且仅当不存在这样的循环路径,位于 依赖图 中,该图属于 该规则集,且该路径包含一个 封闭依赖。

4.4.2 分层算法

以下算法给出一种可能的分层,其依据仅为 规则集。

define stratification(ruleSet):

    let DP = Dependency graph for the rule set.
    let stratumMap be a map from rule to integer

    # 依赖图应满足分层条件。
    # 对无界分层的检查是一项防护措施
    # 用于应对分层条件被违反的情况。
    let limit = num rules + 1
    let maxStratum = 0

    # 初始化 stratumMap
    foreach rule in ruleSet.rules:
        stratumMap.set(rule, 0)
    endfor

    let changed = true
    while changed:
        changed = false
        foreach edge E in DP:
            # 从 pRule 指向 qRule 的带标签的边
            let pRule = source of edge
            let qRule = destination of the edge
            let label = edge label

            if label == "open":
                if stratumMap.get(pRule) < stratumMap.get(qRule):
                    stratumMap.set(pRule, stratumMap.get(qRule))
                    changed = true
                endif
            endif
            if label == "closed":
                if stratumMap.get(pRule) <= stratumMap.get(qRule):
                    let xStratum = 1 + stratumMap.get(qRule)
                    if xStratum > limit:
                        # 违反分层要求
                        error "分层错误"
                    endif
                    stratumMap.set(pRule, xStratum)
                    maxStratum = max(maxStratum, xStratum)
                    changed = true
                endif
            endif
        endfor
    endwhile

    # 初始化结果映射。
    let stratumRules be a map from integer to rules
    foreach i = 0 to maxStratum:
        stratumRules.set(i, {})
    endfor

    # 将 stratumMap 中具有相同层号的规则归集在一起
    foreach rule R in map stratumMap:
        let stratumNum = stratumMap.get(R)
        add R to stratumRules.get(stratumNum)
    endfor

    # 将每个层级划分为 once 和 general
    let stratumLevels be a sequence of pairs of sets of rules
    foreach i = 0 to maxStratum:
        let rules = stratumRules.get(i)
        let once = { R in rules | R is a run-once rule }
        let general = rules \ once
        stratumLevels.set(i, pair(once, general))
    endfor

    the result is stratumLevels
enddefine
注

以下条件的一个推论:分层条件 即一旦一个 单次运行规则 被 求值, 用于确定该规则结果的数据将不会 在后续求值过程中改变。

逐步应用依赖分析和 分层到一个完整规则集,并继续进行 求值的过程,见 6.6 详细 示例。

4.5 处理导入

从 Web 读取文档会涉及安全问题。 是否支持导入规则集由SPARQL-RL 处理器自行决定。 此外,实现可以提供部分支持, 例如仅支持导入某些规则集,而不支持其他规则集, 并且可能从经过验证的副本导入。

以下条件适用于导入处理:

已解析规则集是通过 递归读取另一个规则集的导入中提及的所有规则集, 从该规则集生成的。

示例算法见 B. 导入算法 示例。

5. SPARQL-RL 与 SPARQL 之间的关系

本节为非规范性的。

SRL 和 SPARQL 关系密切。SRL 旨在兼容 SPARQL,并且 SRL 中的许多构造源自或受到启发于 SPARQL 模式匹配。不过,两者也存在一些差异。

在 SRL 中,RULE 变量始终在使用前绑定,无论是用于 FILTER 或 SET 的表达式中,还是用于 三元组模板 中, 这些模板属于 规则头。SPARQL CONSTRUCT 查询和 INSERT 更新会 产生部分结果,如果某个变量出现在 CONSTRUCT 或 INSERT 模板中,但未在 WHERE 子句中被赋值。

其他差异包括:

6. 规则集求值

本节定义了对给定数据求值规则集所产生的结果。 实现不要求使用此算法。 实现可以使用任何能够生成相同结果的算法。

输入:图 G(称为基础图)和规则集 RS。
输出:由推导出的三元组组成的 RDF 图 GI

推导出的三元组不包括 该 基础图中已有的三元组。

6.1 求值定义

解映射
一个 解映射,μ,是一个偏 函数 μ : V → T, 其中 V 是所有变量的集合 而 T 是以下所有项的集合:RDF 项。 μ 的定义域表示 为 dom(μ),它是一个子集 属于 V,且 μ 在其上有定义。术语 解 可用于明确指代一个 解映射 的场合。 用 μ0 表示满足以下条件的解映射: dom(μ0) 为空集。
替换函数

该 替换函数,或简称为一个替换, 是一个函数 subst(μ, 三元组模式) 它返回一个 三元组模式 其中,每个出现位置(位于 三元组模式 中),若对应的变量 var 属于 dom(μ) 则替换为一个 RDF 项,该项由 解映射 为 var 给出。 如果作为结果的三元组模式没有变量,则它是一个 RDF 三元组。

函数 subst(μ, 三元组模板) 的定义类似。变量的每个出现位置,若位于 三元组模板 中,都会被替换为一个 RDF 项,该项由 解映射 为 var 给出。

模板实例函数
该 模板实例函数 是一个函数 replaceBlankNodes(列表,其元素为 三元组模板) 它 返回一个列表,其元素为 三元组模板,其中每个 空白节点 若位于实参中,则被替换为一个新的空白节点。 同一空白节点的多次出现 会被替换为同一个新的空白节点。
求值 图
一个 求值图 是一个 RDF 图,它合并了 基础图 以及规则集求值过程中生成的所有三元组。
图匹配
一个 图匹配 查找以下映射方式: 将一个三元组模式映射到三元组,这些三元组位于一个 RDF 图 中。

令 G 为一个 RDF 图,并令 TP 为一个 三元组模式。 函数 graphMatch(G, TP) 返回一个集合,包含所有可能的 解,这些解 在应用于该三元组模式时,会生成一个三元组,该三元组位于 图 G 中。

令 G 为一个 RDF 图,TP 为一个 三元组模式, 并令 V 为出现在 TP 中的变量的集合。

graphMatch(G, TP) = { μ | dom(μ) = V and subst(μ, TP) is a triple in G }
解兼容性
两个解 S1 和 S2 是 兼容的,如果它们对变量的取值一致, 这些变量为两者共有。

令 S1 和 S2 为解。

compatible(μ1, μ2) = true
                      if forall v in dom(μ1) intersection dom(μ2)
                          μ1(v) = μ2(v)
compatible(μ1, μ2) = false otherwise
解序列
一个 解序列 是一个解的多重集。 该序列没有定义顺序。 它等价于一个无序列表,并且可以包含重复项。
解合并
如果两个解兼容,则这两个解的合并是一个解 该解将每个解的变量映射到一个 RDF 项 该项来自其中一个解。
令 μ1、μ2 为解映射,并令 S1 和 S2 为解序列。
merge(μ1, μ2) = μ such that
                    μ(v) = μ1(v) if v in dom(μ1)
                    μ(v) = μ2(v) otherwise
merge(S1, S2) = { μ |
                    μ1 in S1, μ2 in S2
                    and compatible(μ1, μ2)
                    μ = merge(μ1, μ2) }
注

merge(μ1, μ2) 的定义域 是 domain(μ1) ∪ domain(μ2)。

如果两个解没有共有变量,则它们兼容, 且这两个解的合并是以下两者的并集: μ1 和 μ2。

有效布尔值
函数 EBV(x) 返回有效布尔值 对应于一个 RDF 项。

6.2 求值准备

规则集求值涉及收集所有导入的规则集, 按照 4.5 处理 导入中所述构建一个单一的组合规则集。

接下来,使用以下步骤准备组合规则集 以进行求值。

  1. 检查每个规则是否是良构规则,如 4.2 良构性 条件中所述。
  2. 计算依赖图,如 4.3.2 依赖图算法中所述。
  3. 计算组合规则集的一个分层,如 4.4 分层中所述。 4.4.2 分层算法 给出了一种计算分层的方法。

6.3 表达式求值

一个表达式,无论其用于一个 过滤元素 还是 一个 赋值元素,其求值依据 都是一个 解映射。该解映射提供 表达式中每个变量的 RDF 项值。 以下章节中的良构性要求:4.2 良构性条件 确保表达式中的所有变量 都出现在解映射中。

define evalFunction(F, μ):
    # F 是一个表达式:一个 RDF 项、一个变量,或 op(expr1, ..., exprN)
    #   其中 op 是一个函数或函数形式。
    # μ 是一个解映射

    # 求值过程中的任何错误都会导致 evalFunction 返回一个错误。
    if F is an error:
        return error
    endif
    if F is an RDF term:
        return F
    endif
    if F is a variable:
        # 根据良构性,F ∈ dom(μ)。
        return μ(F)
    endif
    # F 的形式为 F = op(expr1, ..., exprN)
    if op is a functional form (e.g., IF, logical-or):
        # 按 op 的特定规则求值;op 可能只对部分实参求值。
        # 例如,IF(c, t, f) 对 c 求值,然后恰好
        # 对 t 或 f 中的一个求值。
        return the value defined for op over expr1, ..., exprN under μ
    endif
    # op 是一个普通函数:先对所有实参求值。
    return op(evalFunction(expr1, μ), ..., evalFunction(exprN, μ))
enddefine
define evalAggregate(AGGF, SEQ):
@@
    # AGGF 是一个聚合函数,其实参包括一个变量
    # 和一个解序列。它返回一个 RDF 项值。

    # 注意,DISTINCT 仅允许用于 COUNT
    # 特殊情况 COUNT(*)。
enddefine
聚合函数
聚合函数 SPARQL 等价项
COUNT SPARQL COUNT 其中 distinct 设为 false
COUNT DISTINCT SPARQL COUNT 其中 distinct 设为 true
MIN SPARQL MIN
MAX SPARQL MAX
SUM SPARQL SUM
编者注

计算聚合函数时的任何错误都会导致一个 求值错误,且该分组不会添加到结果中。

6.4 规则求值

一条 规则 的求值方式是计算一个 解 序列,其来源为 规则体 然后使用每个 解映射,这些解映射属于该 解序列,以便 利用 规则头 生成三元组。

在规则体中,位于 三元组模式 中的空白节点的行为与变量相同。 这兼容于 SPARQL 图模式匹配。

将空白节点视为变量
空白节点在 三元组 模式 中被视为变量,这些模式属于一个 规则体;具体做法是将每个空白节点替换为一个 未在该规则中使用的变量。 这包括替换位于 三元组项 内的空白节点。 对于同一个空白节点的每次出现, 都使用同一个变量,而对于每个 不同的空白节点,则使用不同的变量。
# 对规则体求值
# 此函数返回一个解序列

define evalRuleElements(B, SEQ, G, GD):
    where
        B is a sequence of rule elements
        SEQ is a solution sequence
        G and GD are RDF graphs

    foreach rule element rElt in B:

        if rElt is a triple pattern element TP:
            X = graphMatch(G, TP)
            SEQ1 = {}
            foreach μ1 in X:
                foreach μ2 in SEQ:
                    if compatible(μ1, μ2):
                        μ3 = merge(μ1, μ2)
                        add μ3 to SEQ1
                    endif
                endfor
            endfor
        endif

        if rElt is a filter element F:
            SEQ1 = {}
            foreach solution μ in SEQ:
                let x = evalFunction(F.expr, μ)
                if x is not an error and EBV(x) is true:
                    add μ to SEQ1
                endif
            endfor
        endif

        if rElt is a negation element N:
            SEQ1 = {}
            foreach solution μ in SEQ:
                S = sequence{ μ }
                if N.evaldata:
                    NEG = evalRuleElements(N.inner, S, G, GD)
                else:
                    NEG = evalRuleElements(N.inner, S, GD, GD)
                endif
                if NEG is empty:
                    add μ to SEQ1
                endif
            endfor
        endif

        if rElt is an assignment element A:
            SEQ1 = {}
            foreach solution μ in SEQ:
                let x = evalFunction(A.expr, μ)
                if x is not an error:
                    # 将映射 A.var -> x 添加到解 μ 中
                    let μ2 be a solution mapping μ ∪ { (A.var, x) }
                    add μ2 to SEQ1
                else:
                    # 错误:丢弃解 μ
                endif
            endfor
        endif

        ## COUNT(*) 如何处理?
        if rElt is a group element GRP:
            SEQ1 = {}
            foreach solution μ in SEQ:
                let μp be the restriction of μ to GRP.keys
                let SEQX = evalRuleElements(GRP.pattern, { μp }, G, GD)
                # 将解映射分配到各个分组。
                let KGRP = empty map of solution mappings to solution sequences
                foreach solution μx in SEQX:
                    let μk be the restriction of μx to GRP.keys
                    if KGRP[μk] is defined:
                        add μx to the solution sequence KGRP[μk]
                    else:
                        KGRP[μk] = solution sequence of one element μx
                    endif
                endfor
                # 对于每个分组,计算聚合函数,并扩展
                # 当前解映射 μ,使用分组键
                # 以及聚合赋值的结果。
                foreach key K of map KGRP:
                    let X be the solution sequence of KGRP[K]
                    let Z be an empty solution mapping
                    foreach aggregation function F in GRP.aggregations:
                        let V = calculate F.function(X)
                        add solution mapping (F.var, V) to Z
                    endfor
                    Z = merge(Z, K)
                    Z = merge(Z, μ)
                    add Z to SEQ1
                endfor
            endfor
        endif
        SEQ = SEQ1
    endfor

    return SEQ
enddefine

define evalRule(R, G, GD):
    where
        R is a well-formed rule
        G and GD are RDF graphs
    let B be R.body
        treating blank nodes as variables.

    # 仅包含一个不映射任何变量的解的解序列。
    let SEQ0 be the solution sequence { μ0 }

    if R.evaldata:
        let SEQ = evalRuleElements(B, SEQ0, G, GD)
    else:
        let SEQ = evalRuleElements(B, SEQ0, GD, GD)
    endif

    # 对规则头求值
    let OUT = empty set
    foreach μ in SEQ:
        let S = {}
        # 将规则头中的每个空白节点替换为一个新的空白节点
        let HT = replaceBlankNodes(R.head)
        foreach triple template TT in HT:
            let triple = subst(μ, TT)
            add triple to S
        endfor
        OUT = OUT ∪ S
    endfor
    return OUT
enddefine
注

OUT 可能包含也存在于 基础图 中的三元组。

6.5 规则集求值

对规则集求值,定义为执行该规则集某个层,这些层来自该规则集的 分层,其中每一层都必须 完整执行, 并按顺序执行,然后才能进入下一层。 对层求值时,首先执行 该层中的每条仅运行一次的规则,然后反复执行该层的 一般规则,直到不再有新的三元组 产生。

let G0 be the input base graph
let RS be the rule set
let D be the graph of all DATA triples in RS

Apply stratification to RS

let LS be the sequence of layers after stratification

# 推理图
let GI = { t ∈ D | t ∉ G0 }

# 求值图:基础图和数据块
let GE = G0 ∪ D

foreach stratum ST in LS:
    foreach rule R in ST.once:
        let X = evalRule(R, GE, G0)
        let Y = { t ∈ X | t ∉ GE }
        GI = GI ∪ Y
        GE = GE ∪ Y
    endfor

    let finished = false
    while ! finished:
        finished = true
        foreach rule R in ST.general:
            let X = evalRule(R, GE, G0)
            let Y = { t ∈ X | t ∉ GE }
            if Y is not empty:
                finished = false
                GI = GI ∪ Y
                GE = GE ∪ Y
            endif
        endfor
    endwhile
endfor
the result is GI

6.6 完整示例

本节为非规范性的。

本节逐步展示一个完整 规则集的求值过程:确定规则依赖 (4.3 规则 依赖)、 计算分层 (4.4 分层), 并依次对每个层求值以产生 推理图 (6.5 规则集 求值)。

该示例描述了软件组件及其依赖关系: 一个前端依赖于应用服务器,而应用服务器依赖于 数据库和日志库。数据库存在一个已知 漏洞。规则沿依赖链传播漏洞暴露关系, 为暴露于严重漏洞的组件赋予 :criticallyExposed 状态,为不严重的组件赋予 :safeToDeploy 状态,并为每个严重暴露的组件创建 通知。 (实际中,一个漏洞是否影响组件 取决于部署的版本;该示例省略了版本信息,并 直接声明漏洞。) 为简洁起见,基础图直接声明 :dependsOn 三元组, 而不是像 3.1 三元组 模式中那样从更具体的关系派生它们。

这些规则在注释中标记为 R1 到 R5,本节其余部分 使用这些标签引用它们。 它们是从 3.2 递归到 3.5 赋值 和创建 RDF 术语中引入的规则。 该规则集没有 IMPORTS,因此导入处理 不会改变它;也没有 DATA 块,因此求值仅 从基础图开始。

6.6.1 确定 依赖

将每个规则主体中的每个三元组模式与 每个规则头中的三元组模板进行比较,以确定哪些规则 依赖于哪些规则 (4.3 规则 依赖)。

  • R1 的主体是单个三元组模式 ?x :hasVulnerability ?v。没有任何规则头包含能 生成谓语为 :hasVulnerability 的三元组的三元组模板, 因此该模式只能匹配数据。 R1 没有依赖。
  • R2 的主体有两个三元组模式。 ?x :dependsOn ?y 无法匹配任何规则头,因此它只能匹配 数据。?y :exposedTo ?v 与 R1 的头模板 ?x :exposedTo ?v 匹配,也与 R2 自身的头匹配。 两次出现都是三元组模式元素 (它们不出现在否定元素内部,并且 R2 不是单次运行规则),因此 R2 对 R1 具有开放依赖,并且 对自身具有开放依赖。 R2 对自身的依赖使 R2 成为递归规则。
  • 在 R3 的主体中,模式 ?x :exposedTo ?v 与 R1 和 R2 的头匹配,因此 R3 对它们各自具有开放 依赖。模式 ?v :severity ?s 不匹配任何 规则头。
  • 在 R4 的主体中,模式 ?x rdf:type :Component 不匹配任何规则头:唯一一个谓语为 rdf:type 的头模板位于 R5 中,而它的宾语 :Notification 是 与 :Component 不同的 RDF 术语,因此任何生成的 三元组都无法匹配。 模式 ?x :status :criticallyExposed 出现在 否定元素 NOT { ?x :status :criticallyExposed } 中,并且 与 R3 的头模板匹配。因为该模式 出现在否定元素中,所以 R4 对 R3 具有闭合 依赖。
  • 在 R5 的主体中,模式 ?x :status :criticallyExposed 与 R3 的头匹配。R5 的头包含一个 空白节点,因此它对 R3 的依赖是闭合 依赖,即使该模式是普通的 三元组模式元素。 任何规则主体中的模式都不匹配 R5 的头模板, 因此没有规则依赖于 R5。

因此,依赖图有五个顶点和六条 边:

  • R2 → R1(开放)
  • R2 → R2(开放)
  • R3 → R1(开放)
  • R3 → R2(开放)
  • R4 → R3(闭合)
  • R5 → R3(闭合)

尽管 R4 和 R5 都不直接依赖于 R1 或 R2,但它们各自通过 R3 对二者都具有传递依赖。

完整示例规则集的依赖图 五个顶点,分别标记为 R1、R2、R3、R4 和 R5。 虚线箭头表示开放依赖:从 R2 到 R1、 从 R2 到自身、从 R3 到 R1,以及从 R3 到 R2。 实线箭头表示闭合依赖:从 R4 到 R3 以及从 R5 到 R3。 R2 R1 R3 R4 R5 开放依赖 闭合依赖
图 1 完整示例规则集的依赖图。 开放依赖绘制为虚线箭头,闭合 依赖绘制为实线箭头。每条边都从 主体包含匹配三元组模式的规则指向 头部能够生成匹配三元组的规则。

图中唯一的循环是自环 R2 → R2, 它是开放依赖。没有循环涉及闭合 依赖,因此满足分层条件, 并且该规则集具有明确定义的结果。

6.6.2 计算 分层

按照 分层算法, 每个规则都从第 0 层开始。然后反复检查各条边, 直到没有任何变化:

  • 一条开放边要求源规则处于 与其目标相同或更高的层。所有四条开放边在 每个规则都处于第 0 层时已经满足此条件,因此它们 不会引起任何变化。
  • 一条闭合边要求源规则处于 严格高于其目标的层,因为目标 必须先运行至完成。边 R4 → R3 (闭合)发现 R4 和 R3 都处于第 0 层,因此 R4 被 移到第 1 层。同样,边 R5 → R3 (闭合)将 R5 移到第 1 层。

再次遍历各条边不会产生变化,因此这些层 已最终确定。然后将每个层划分为 单次运行规则和一般规则。 R5 的头中有一个空白 节点,因此它是单次运行规则;该规则集的其他规则 都不是单次运行规则。 分层如下:

  • 第 0 层:一般规则 R1、R2、R3;没有单次运行规则
  • 第 1 层:单次运行规则 R5;一般规则 R4

此顺序体现了较高 层中规则的意图。一个组件是否可以安全部署,以及哪些 组件需要通知,只有在 所有 :status :criticallyExposed 三元组都已派生后才能确定 —— 而派生这些三元组的规则 R3 会在 较低的层中完成,同时完成的还有向它提供数据的规则 R1 和 R2。

6.6.3 对各层求值

求值遵循 6.5 规则集 求值的算法。 求值图 GE 以基础图开始,而 推理图 GI 初始为空。每个层都会按顺序求值至 完成:该层的规则按轮次求值, 并重复这些轮次,直到某一轮不产生新的三元组。 该算法并未固定同一轮中 对一个层内规则求值的顺序;此处跟踪使用 R1、R2、R3 的顺序。不同的顺序可能会将相同的 推理分散到不同数量的轮次中,但由于 第 0 层会运行至完成,因此会产生相同的最终图。

由于此规则集没有 DATA 块,因此在求值的每个时刻,GE 都是 G0 加 GI。因此,下面的跟踪 只记录 GI,显示求值进行时它的状态,并 用产生每个新三元组的规则进行标记。

第 0 层,第一轮。

  • R1:主体匹配 :db :hasVulnerability :vuln1,绑定 ?x = :db 和 ?v = :vuln1。 头部产生 :db :exposedTo :vuln1。 该三元组不在 GE 中,因此被添加到 GE 和 GI 中。
  • R2:主体将 ?x :dependsOn ?y 与 ?y :exposedTo ?v 连接。GE 现在包含 :db :exposedTo :vuln1,因此连接得到一个解: ?x = :app、?y = :db 和 ?v = :vuln1 (来自 :app :dependsOn :db)。 头部产生 :app :exposedTo :vuln1,这是新的, 因而被添加。此时还没有 :frontend 的解,因为 在此次对 R2 的求值开始时, :app :exposedTo :vuln1 尚不在 GE 中。
  • R3:?x :exposedTo ?v 现在匹配 :db 和 :app; 与 :vuln1 :severity 9.1 连接并应用 FILTER(?s >= 9.0) 后保留两个解。 头部产生 :db :status :criticallyExposed 和 :app :status :criticallyExposed,两者都是新的。
# 第 0 层第一轮后的 GI
:db  :exposedTo :vuln1 .                    # 新增 (R1)
:app :exposedTo :vuln1 .                    # 新增 (R2)
:db  :status :criticallyExposed .           # 新增 (R3)
:app :status :criticallyExposed .           # 新增 (R3)

产生了新的三元组,因此第 0 层的求值 继续进行下一轮。

第 0 层,第二轮。

  • R1:与之前一样进行匹配;:db :exposedTo :vuln1 已经 在 GE 中,因此不会产生新内容。
  • R2:连接现在还会得到 ?x = :frontend、?y = :app 和 ?v = :vuln1,产生 新的三元组 :frontend :exposedTo :vuln1。 暴露关系用了两轮沿依赖链传播了两步。
  • R3:产生新的三元组 :frontend :status :criticallyExposed。
# 第 0 层第二轮后的 GI
:db       :exposedTo :vuln1 .
:app      :exposedTo :vuln1 .
:frontend :exposedTo :vuln1 .               # 新增 (R2)
:db       :status :criticallyExposed .
:app      :status :criticallyExposed .
:frontend :status :criticallyExposed .      # 新增 (R3)

第 0 层,第三轮。 现在每个规则的每个解都只产生已经 位于 GE 中的三元组。GI 保持不变。 没有新的三元组意味着第 0 层已经完成:所有 能够派生的 :exposedTo 三元组以及所有 :status :criticallyExposed 三元组 都已经派生出来。

第 1 层,单次运行规则。 首先对该层的单次运行规则求值, 每条规则恰好求值一次。

  • R5:主体匹配三个 :status :criticallyExposed 三元组,得到解 ?x = :db、?x = :app 和 ?x = :frontend。对于每个解,实例化头部 会创建一个新的空白节点,每个解产生两个三元组, 它们构成每个严重暴露 组件的通知。即使该层中的 一般规则随后会被 重复求值,R5 也不会再次求值。

第 1 层,一般规则的第一轮。

  • R4:模式 ?x rdf:type :Component 匹配 :frontend、:app、:db 和 :logger。 (它不会匹配刚刚由 R5 创建的 rdf:type :Notification 三元组。) 否定元素 NOT { ?x :status :criticallyExposed } 拒绝 :frontend、:app 和 :db 的解,因为 GE 中分别包含它们的 :status :criticallyExposed 三元组。 只有解 ?x = :logger 保留下来,产生新的 三元组 :logger :status :safeToDeploy。
# 第 1 层后的 GI
:db       :exposedTo :vuln1 .
:app      :exposedTo :vuln1 .
:frontend :exposedTo :vuln1 .
:db       :status :criticallyExposed .
:app      :status :criticallyExposed .
:frontend :status :criticallyExposed .
_:n1      rdf:type :Notification .          # 新增 (R5)
_:n1      :concerns :db .                   # 新增 (R5)
_:n2      rdf:type :Notification .          # 新增 (R5)
_:n2      :concerns :app .                  # 新增 (R5)
_:n3      rdf:type :Notification .          # 新增 (R5)
_:n3      :concerns :frontend .             # 新增 (R5)
:logger   :status :safeToDeploy .           # 新增 (R4)

第 1 层,一般规则的第二轮。 没有新的三元组。求值完成,并且上面所示的 GI 是最终得到的推理图。

正是分层使这一结果可靠。 如果 R4 在第 0 层第一轮期间、 :frontend :status :criticallyExposed 被派生之前求值,它会 错误地得出 :frontend :status :safeToDeploy,而 R5 会为 :db 和 :app 创建通知, 却不会为 :frontend 创建通知。 将二者都推迟到第 1 层意味着它们能够看到完整的 :status :criticallyExposed 三元组集合,因此每个层中 规则的求值顺序不会影响 最终结果。

7. SPARQL-RL 语法

一个 SPARQL-RL 文档 是一个 RDF 字符串,使用 UTF-8 编码 [RFC3629], 且符合语法和 附加约束,其定义见 7.6 语法。只有 Unicode 标量值, 即 U+0000 至 U+D7FF 和 U+E000 至 U+10FFFF 范围内的值, 才允许使用。 这排除了 代理码点, 即 U+D800 至 U+DFFF 的范围。

7.1 版本声明

一个 版本标签 是一个字符串,用于标识语法和语义方面的 SPARQL-RL 符合性。

版本标签
版本标签
"1.2"

版本声明是一种提示。它应出现在 文档的靠前位置,并位于任何依赖该版本的特性之前。

多个 VERSION 指令 可以出现在一个 SPARQL-RL 文档 中。 每个指令适用于文档中该指令之后的部分, 直到遇到另一个指令或到达文档末尾。

版本标签 也可通过 version 参数给出,该参数属于 媒体类型。如果当前没有 VERSION 指令,则 考虑作为媒体类型的一部分指定的版本。

7.2 空白字符

空白字符 (产生式 WS)用于 分隔两个原本会被错误地 识别为一个终结符的终结符。 下文以大写字母表示的规则名称指明空白字符在何处 具有重要意义;这些规则构成了用于构建 SPARQL-RL 解析器时可选用的一组终结符。

空白字符在产生式 String中具有重要意义。

7.3 注释

注释以位于 IRIREF、 STRING_LITERAL1、 STRING_LITERAL2、 STRING_LITERAL_LONG1 或 STRING_LITERAL_LONG2 之外的 # 开始, 并持续到行尾(由 LF 或 CR 标记), 如果注释标记之后没有行尾,则持续到文件末尾。 注释被视为空白字符。

7.4 IRI 引用

相对 IRI 引用使用基 IRI 按照 统一资源标识符(URI):通用 语法 [RFC3986] 进行解析,仅使用 第 5.2 节中的基本算法。 不执行基于语法的规范化或基于方案的规范化 (RFC3986 第 6.2.2 节和第 6.2.3 节中所述)。 IRI 引用中额外允许的字符按照与 URI 引用中非保留字符相同的方式处理,依据 国际化资源标识符 (IRI) [RFC3987] 第 6.5 节。

BASE 指令定义用于解析 相对 IRI 引用的基 IRI,依据 [RFC3986] 第 5.1.1 节,“嵌入内容中的基 URI ”。 第 5.1.2 节,“来自封装 实体的基 URI” 定义作用域内基 IRI 如何来自封装文档, 例如具有 xml:base 指令的 SOAP 信封或具有 Content-Location 标头的 MIME 多部分文档。 在 5.1.3,“来自检索 URI 的基 URI”中标识的“检索 URI”, 是从中检索特定SPARQL-RL 文档的 URL。 如果上述内容均未指定基 URI,则使用默认 基 URI(第 5.1.4 节,“默认基 URI”)。 每个 BASE 指令都会设置一个新的作用域内基 URI, 相对于前一个基 URI。

7.5 转义序列

SPARQL-RL 文档中使用三种形式的转义:

每种转义序列可使用的上下文
数字
转义
字符串
转义
保留字符
转义
IRI, 用作RDF 术语、 PREFIX、 或 BASE 声明 是 否 否
本地名称 否 否 是
字符串 是 是 否
注

百分号编码序列位于 IRI 的字符范围内, 并且在本地名称中被明确允许。 它们表现为一个 % 后跟两个十六进制字符,并表示 相同的三个字符序列。这些序列在处理期间不会 被解码。 写作 <http://a.example/%66oo-bar> 的术语 表示 IRI http://a.example/%66oo-bar, 而不是 IRI http://a.example/foo-bar。 写作 ex:%66oo-bar 且带有前缀 PREFIX ex: <http://a.example/> 的术语 同样表示 IRI http://a.example/%66oo-bar。

7.6 语法

此处使用的 EBNF 定义于 XML 1.0 [EBNF-NOTATION]。

注:

  1. 该语法的入口点是 RuleSet。
  2. 除以下关键字外,所有关键字均不区分大小写: 'a', 它区分大小写。
  3. 转义序列 UCHAR 和 ECHAR 均区分大小写。
  4. DATA 块中不允许使用变量。
  5. 对输入进行词法分析并选择语法规则时,采用最长匹配。
  6. SPARQL-RL 语法是 LL(1) 和 LALR(1) 语法,前提是名称采用大写字母的规则 被用作终结符。
[1] RuleSet ::= RuleOrDataBlock
[2] RuleOrDataBlock ::= Prologue ( RuleOrData+ ( Prologue1 RuleOrData* )* )?
[3] RuleOrData ::= Rule | Data
[4] Prologue ::= Prologue1*
[5] Prologue1 ::= BaseDecl | PrefixDecl | VersionDecl | ImportsDecl
[6] BaseDecl ::= 'BASE' IRIREF
[7] PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
[8] VersionDecl ::= 'VERSION' VersionSpecifier
[9] VersionSpecifier ::= STRING_LITERAL1 | STRING_LITERAL2
[10] ImportsDecl ::= 'IMPORTS' iri
[11] Rule ::= 'RULE' iri? HeadTemplate 'WHERE' 'DATA'? BodyPattern
[12] Data ::= 'DATA' '{' DataTriplesBlock? '}'
[13] HeadTemplate ::= '{' HeadTemplateBlock? '}'
[14] BodyPattern ::= '{' BodyTriplesBlock? ( BodyNotTriples '.'? BodyTriplesBlock? )* '}'
[15] BodyNotTriples ::= Filter | Negation | Assignment | GroupAggregate
[16] Filter ::= 'FILTER' Constraint
[17] Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
[18] Negation ::= 'NOT' 'DATA'? '{' NegationPattern '}'
[19] NegationPattern ::= BodyTriplesBlock? ( NegationPatternNotTriples '.'? BodyTriplesBlock? )*
[20] NegationPatternNotTriples ::= Filter
[21] Assignment ::= 'SET' '(' Var ':=' Expression ')'
[22] GroupAggregate ::= 'GROUP' ( 'BY' ListVars )? '{' GroupPattern '}' ( ANON | '[' AggregationAssignments ']' )
[23] GroupPattern ::= BodyTriplesBlock? ( GroupPatternNotTriples '.'? BodyTriplesBlock? )*
[24] GroupPatternNotTriples ::= Filter | Assignment
[25] ListVars ::= Var+
[26] AggregationAssignments ::= ( AggregationAssignment ( ',' AggregationAssignment )* )?
[27] AggregationAssignment ::= Var ':=' AggregationFunction
[28] AggregationFunction ::= ( 'COUNT' '(' DISTINCT? VarOrStar ')' | 'MIN' '(' Var ')' | 'MAX' '(' Var ')' | 'SUM' '(' Var ')' )
[29] VarOrStar ::= Var | '*'
[30] DataTriplesBlock ::= TriplesSameSubjectData ( '.' DataTriplesBlock? )?
[31] TriplesSameSubjectData ::= RDFTermData PropertyListNotEmptyData | TriplesNodeData PropertyListData | ReifiedTripleBlockData
[32] PropertyListData ::= PropertyListNotEmptyData?
[33] PropertyListNotEmptyData ::= VerbData ObjectListData ( ';' ( VerbData ObjectListData )? )*
[34] VerbData ::= iri | 'a'
[35] ObjectListData ::= ObjectData ( ',' ObjectData )*
[36] ObjectData ::= GraphNodeData AnnotationData
[37] GraphNodeData ::= RDFTermData | TriplesNodeData | ReifiedTripleData
[38] TriplesNodeData ::= CollectionData | BlankNodePropertyListData
[39] BlankNodePropertyListData ::= '[' PropertyListNotEmptyData ']'
[40] CollectionData ::= '(' GraphNodeData+ ')'
[41] AnnotationData ::= ( ReifierData | AnnotationBlockData )*
[42] AnnotationBlockData ::= '{|' PropertyListNotEmptyData '|}'
[43] ReifierData ::= '~' ReifierIdData?
[44] ReifierIdData ::= iri | BlankNode
[45] ReifiedTripleBlockData ::= ReifiedTripleData PropertyListData
[46] ReifiedTripleData ::= '<<' ReifiedTripleSubjectData VerbData ReifiedTripleObjectData ReifierData? '>>'
[47] ReifiedTripleSubjectData ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTripleData | TripleTermData
[48] ReifiedTripleObjectData ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTripleData | TripleTermData
[49] TripleTermData ::= '<<(' TripleTermSubjectData VerbData TripleTermObjectData ')>>'
[50] TripleTermSubjectData ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTermData
[51] TripleTermObjectData ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTermData
[52] HeadTemplateBlock ::= TriplesBlockTemplate
[53] TriplesBlockTemplate ::= TriplesSameSubjectTemplate ( '.' TriplesBlockTemplate? )?
[54] TriplesSameSubjectTemplate ::= VarOrRDFTerm PropertyListNotEmptyTemplate | TriplesNodeTemplate PropertyListTemplate | ReifiedTripleBlockTemplate
[55] PropertyListTemplate ::= PropertyListNotEmptyTemplate?
[56] PropertyListNotEmptyTemplate ::= Verb ObjectListTemplate ( ';' ( Verb ObjectListTemplate )? )*
[57] ObjectListTemplate ::= ObjectTemplate ( ',' ObjectTemplate )*
[58] ObjectTemplate ::= GraphNodeTemplate AnnotationTemplate
[59] GraphNodeTemplate ::= VarOrRDFTerm | TriplesNodeTemplate | ReifiedTriple
[60] TriplesNodeTemplate ::= CollectionTemplate | BlankNodePropertyListTemplate
[61] BlankNodePropertyListTemplate ::= '[' PropertyListNotEmptyTemplate ']'
[62] CollectionTemplate ::= '(' GraphNodeTemplate+ ')'
[63] AnnotationTemplate ::= ( Reifier | AnnotationBlockTemplate )*
[64] AnnotationBlockTemplate ::= '{|' PropertyListNotEmptyTemplate '|}'
[65] ReifiedTripleBlockTemplate ::= ReifiedTriple PropertyListTemplate
[66] BodyTriplesBlock ::= TriplesBlockPattern
[67] TriplesBlockPattern ::= TriplesSameSubjectPattern ( '.' TriplesBlockPattern? )?
[68] ReifiedTripleBlockPattern ::= ReifiedTriple PropertyListPattern
[69] TriplesSameSubjectPattern ::= VarOrRDFTerm PropertyListNotEmptyPattern | TriplesNodePattern PropertyListPattern | ReifiedTripleBlockPattern
[70] PropertyListPattern ::= PropertyListNotEmptyPattern?
[71] PropertyListNotEmptyPattern ::= ( VerbPath | Var ) ObjectListPattern ( ';' ( ( VerbPath | Var ) ObjectListPattern )? )*
[72] ObjectListPattern ::= ObjectPattern ( ',' ObjectPattern )*
[73] ObjectPattern ::= GraphNodePattern AnnotationPattern
[74] TriplesNodePattern ::= CollectionPattern | BlankNodePropertyListPattern
[75] BlankNodePropertyListPattern ::= '[' PropertyListNotEmptyPattern ']'
[76] CollectionPattern ::= '(' GraphNodePattern+ ')'
[77] AnnotationPattern ::= ( Reifier | AnnotationBlockPattern )*
[78] AnnotationBlockPattern ::= '{|' PropertyListNotEmptyPattern '|}'
[79] GraphNodePattern ::= VarOrRDFTerm | TriplesNodePattern | ReifiedTriple
[80] Reifier ::= '~' ReifierId?
[81] ReifierId ::= Var | iri | BlankNode
[82] ReifiedTriple ::= '<<' ReifiedTripleSubject Verb ReifiedTripleObject Reifier? '>>'
[83] ReifiedTripleSubject ::= Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTriple | TripleTerm
[84] ReifiedTripleObject ::= Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTriple | TripleTerm
[85] TripleTerm ::= '<<(' TripleTermSubject Verb TripleTermObject ')>>'
[86] TripleTermSubject ::= Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTerm
[87] TripleTermObject ::= Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTerm
[88] Verb ::= VarOrIri | 'a'
[89] VerbPath ::= Path
[90] Path ::= PathSequence
[91] PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse )*
[92] PathEltOrInverse ::= PathElt | '^' PathElt
[93] PathElt ::= ( iri | 'a' | '(' Path ')' )
[94] RDFTermData ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | TripleTermData
[95] VarOrRDFTerm ::= Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | TripleTerm
[96] VarOrIri ::= Var | iri
[97] Var ::= VAR1 | VAR2
[98] RDFLiteral ::= String ( LANG_DIR | '^^' iri )?
[99] NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
[100] NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
[101] NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
[102] NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
[103] BooleanLiteral ::= 'true' | 'false'
[104] String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
[105] iri ::= IRIREF | PrefixedName
[106] PrefixedName ::= PNAME_LN | PNAME_NS
[107] BlankNode ::= BLANK_NODE_LABEL | ANON
[108] Expression ::= ConditionalOrExpression
[109] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
[110] ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
[111] ValueLogical ::= RelationalExpression
[112] RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression | 'IN' ExpressionList | 'NOT' 'IN' ExpressionList )?
[113] NumericExpression ::= AdditiveExpression
[114] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | ( NumericLiteralPositive | NumericLiteralNegative ) ( ( '*' UnaryExpression ) | ( '/' UnaryExpression ) )* )*
[115] MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
[116] UnaryExpression ::= '!' PrimaryExpression
| '+' PrimaryExpression
| '-' PrimaryExpression
| PrimaryExpression
[117] PrimaryExpression ::= BrackettedExpression | BuiltInCall | iriOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var | ExprTripleTerm
[118] FunctionCall ::= iri ArgList
[119] ArgList ::= NIL | '(' Expression ( ',' Expression )* ')'
[120] ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')'
[121] iriOrFunction ::= iri ArgList?
[122] ExprTripleTerm ::= '<<(' ExprTripleTermSubject Verb ExprTripleTermObject ')>>'
[123] ExprTripleTermSubject ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | Var
[124] ExprTripleTermObject ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | Var | ExprTripleTerm
[125] BrackettedExpression ::= '(' Expression ')'
[126] BuiltInCall ::= 'STR' '(' Expression ')'
| 'LANG' '(' Expression ')'
| 'LANGMATCHES' '(' Expression ',' Expression ')'
| 'LANGDIR' '(' Expression ')'
| 'DATATYPE' '(' Expression ')'
| 'IRI' '(' Expression ')'
| 'URI' '(' Expression ')'
| 'BNODE' ( '(' Expression ')' | NIL )
| 'ABS' '(' Expression ')'
| 'CEIL' '(' Expression ')'
| 'FLOOR' '(' Expression ')'
| 'ROUND' '(' Expression ')'
| 'CONCAT' ExpressionList
| 'SUBSTR' '(' Expression ',' Expression ( ',' Expression )? ')'
| 'STRLEN' '(' Expression ')'
| 'REPLACE' '(' Expression ',' Expression ',' Expression ( ',' Expression )? ')'
| 'UCASE' '(' Expression ')'
| 'LCASE' '(' Expression ')'
| 'ENCODE_FOR_URI' '(' Expression ')'
| 'CONTAINS' '(' Expression ',' Expression ')'
| 'STRSTARTS' '(' Expression ',' Expression ')'
| 'STRENDS' '(' Expression ',' Expression ')'
| 'STRBEFORE' '(' Expression ',' Expression ')'
| 'STRAFTER' '(' Expression ',' Expression ')'
| 'YEAR' '(' Expression ')'
| 'MONTH' '(' Expression ')'
| 'DAY' '(' Expression ')'
| 'HOURS' '(' Expression ')'
| 'MINUTES' '(' Expression ')'
| 'SECONDS' '(' Expression ')'
| 'TIMEZONE' '(' Expression ')'
| 'TZ' '(' Expression ')'
| 'NOW' NIL
| 'UUID' NIL
| 'STRUUID' NIL
| 'IF' '(' Expression ',' Expression ',' Expression ')'
| 'STRLANG' '(' Expression ',' Expression ')'
| 'STRLANGDIR' '(' Expression ',' Expression ',' Expression ')'
| 'STRDT' '(' Expression ',' Expression ')'
| 'sameTerm' '(' Expression ',' Expression ')'
| 'isIRI' '(' Expression ')'
| 'isURI' '(' Expression ')'
| 'isBLANK' '(' Expression ')'
| 'isLITERAL' '(' Expression ')'
| 'isNUMERIC' '(' Expression ')'
| 'hasLANG' '(' Expression ')'
| 'hasLANGDIR' '(' Expression ')'
| 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
| 'isTRIPLE' '(' Expression ')'
| 'TRIPLE' '(' Expression ',' Expression ',' Expression ')'
| 'SUBJECT' '(' Expression ')'
| 'PREDICATE' '(' Expression ')'
| 'OBJECT' '(' Expression ')'

终结符的产生式:

[127] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20] | UCHAR )* '>'
[128] PNAME_NS ::= PN_PREFIX? ':'
[129] PNAME_LN ::= PNAME_NS PN_LOCAL
[130] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)?
[131] VAR1 ::= '?' VARNAME
[132] VAR2 ::= '$' VARNAME
[133] LANG_DIR ::= '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ('--' [a-zA-Z]+)?
[134] INTEGER ::= [0-9]+
[135] DECIMAL ::= [0-9]* '.' [0-9]+
[136] DOUBLE ::= ( ([0-9]+ ('.'[0-9]*)? ) | ( '.' ([0-9])+ ) ) [eE][+-]?[0-9]+
[137] INTEGER_POSITIVE ::= '+' INTEGER
[138] DECIMAL_POSITIVE ::= '+' DECIMAL
[139] DOUBLE_POSITIVE ::= '+' DOUBLE
[140] INTEGER_NEGATIVE ::= '-' INTEGER
[141] DECIMAL_NEGATIVE ::= '-' DECIMAL
[142] DOUBLE_NEGATIVE ::= '-' DOUBLE
[143] STRING_LITERAL1 ::= "'" ( ([^#x27#x5C#xA#xD]) | ECHAR | UCHAR )* "'"
[144] STRING_LITERAL2 ::= '"' ( ([^#x22#x5C#xA#xD]) | ECHAR | UCHAR )* '"'
[145] STRING_LITERAL_LONG1 ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''"
[146] STRING_LITERAL_LONG2 ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""'
[147] ECHAR ::= '\' [tbnrf\"']
[148] UCHAR ::= ('\u' HEX HEX HEX HEX) | ('\U' HEX HEX HEX HEX HEX HEX HEX HEX)
[149] NIL ::= '(' WS* ')'
[150] WS ::= #x20 | #x9 | #xD | #xA
[151] ANON ::= '[' WS* ']'
[152] PN_CHARS_BASE ::= [A-Z] | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
[153] PN_CHARS_U ::= PN_CHARS_BASE | '_'
[154] VARNAME ::= ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] )*
[155] PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
[156] PN_PREFIX ::= PN_CHARS_BASE ((PN_CHARS|'.')* PN_CHARS)?
[157] PN_LOCAL ::= (PN_CHARS_U | ':' | [0-9] | PLX ) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX) )?
[158] PLX ::= PERCENT | PN_LOCAL_ESC
[159] PERCENT ::= '%' HEX HEX
[160] HEX ::= [0-9] | [A-F] | [a-f]
[161] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '/' | '?' | '#' | '@' | '%' )

此语法的文本版本可在以下文件中获取: sparql-rl-grammar.bnf。

7.7 选定的终结符字面 字符串

本文档使用了一些终结符字面字符串 [EBNF-NOTATION]。为明确用于 这些 终结符字面字符串的 Unicode 码点,下表列出了所使用的具体 字符。

代码 字形 说明
U+000A LF 换行符
U+000D CR 回车符
U+0023 # 数字符号
U+0025 % 百分号
U+005C \ 反斜杠

A. 将 SPARQL-RL 转换为 SPARQL

议题 1303: SRL: 提供一个算法 来展示如何将 SRL 转换为 SPARQL 查询 SPARQL-RL

提供一个算法来展示如何将 SPARQL-RL 转换为 SPARQL 查询。

B. 导入算法示例

本节为非规范性内容。

以下算法展示了一种解析 IMPORTS 语句的方法, 即递归访问所有被引用的文档。

两个规则集 RS1 和 RS2 的规则集合并 会产生一个规则集 MR,其定义如下:

let RS be a rule set
let V = {}
if RS has a location:
    V = { location of RS }
endif

define ruleSetMerge(rule set RS1, rule set RS2):
    let MR be a rule set
    MR.rules = RS1.rules ∪ RS2.rules
    MR.data = rdf_merge(RS1.data, RS2.data)
    MR.imports = {}
    the result is MR
enddefine

define imports(rule set RS):
    let RS2 be a rule set formed from RS.rules and RS.data
    foreach URL x in RS.imports:
        if x ∉ V:
            V = V ∪ { x }
            read rule set RS3 from URL x
            RS2 = ruleSetMerge(RS2, imports(RS3))
        endif
    endfor
    the result is RS2
enddefine

result is imports(RS)

其中 rdf_merge 是 RDF 合并 操作。

C. 互联网媒体类型和文件 扩展名

SPARQL-RL 的互联网媒体类型(以前称为 MIME 类型) 是 "application/sparql-rl"。

以下信息已提交给互联网工程 指导委员会(IESG),以供审查、批准并在 IANA 注册。

类型名称:
application
子类型名称:
sparql-rl
必需参数:
无
可选参数:
version
此参数是可选的。 如果存在,version 的可接受值在 版本标签中定义。
profile
此参数是可选的,用于包含附加信息。 在不了解配置文件的情况下处理资源表示时, 它不会改变该资源表示的语义。 profile 参数的值是一个由空格分隔的非空 URI 列表。 有关更多信息和背景,请参阅 [RFC6906]。
编码注意事项:
SPARQL-RL 的语法使用 Unicode 中的码点表示 [UNICODE]。编码始终为 UTF-8 [RFC3629]。
Unicode 码点也可以使用 \uXXXX(U+0 至 U+FFFF)或 \UXXXXXXXX 语法(适用于 U+10000 及以上码点) 表示,其中 X 是一个十六进制数字 [0-9A-Fa-f], 但不包括 U+D800 至 U+DFFF, 即代理码点。
安全注意事项:
请参阅D. 安全注意事项以及 UTF-8, ISO 10646 的一种转换格式 [RFC3629] 第 7 节“安全 注意事项”。
互操作性注意事项:
目前没有已知的互操作性问题。
已发布的规范:
本规范。
附加信息:
魔数:
SPARQL-RL 文件可能在文档 开头附近包含字符串 'PREFIX' (不区分大小写)。
文件扩展名:
".srl"
基础 URI:
SPARQL-RL 的 'BASE <IRIref>' 项可以更改 用于解析该语言中 IRIref 的当前基础 URI。
用于获取更多信息的联系电子邮件地址:
数据形状工作组 <public-shacl@w3.org>
预期用途:
COMMON
使用限制:
无
作者/变更控制方:
SPARQL-RL 规范是 万维网联盟数据形状工作组的工作成果。 W3C 拥有本规范的变更控制权。

D. 安全注意事项

本节为非规范性内容。

SPARQL-RL 文档可以包含 IMPORTS 语句,这些语句引用其他 SPARQL-RL 文档,并将其内容导入当前 文档。 如果导入的文档本身包含自己的 IMPORTS 语句, 则这些语句引用的文档也会被导入。

尽管有助于模块化 规则集,但使用 IMPORTS 语句可能 引入安全风险。 风险包括但不限于:规则集 在求值时导致过量计算,无论是恶意还是意外所致; 以及 HTTP 请求被拦截并返回不同的文档, 包括过时的副本。

将 SPARQL-RL 规则集应用于 RDF 图可能导致 大量计算和内存消耗,这可能被利用 来造成拒绝服务。 应用程序应注意限制计算量和 内存使用量,这些消耗可能由应用 SPARQL-RL 规则集造成。

SPARQL-RL 语法使用 UTF-8 编码 [RFC3629],并且 允许 在字符串数据中使用未转义的控制字符。 虽然本规范不会直接向最终用户暴露此内容, 但它可能通过用户代理呈现,这可能导致所呈现的文本 因此类字符而产生误导。

SPARQL-RL 可用于处理和创建任意应用数据; 安全考虑因素会因使用领域而异。 适用于文本的安全工具和协议 (例如,PGP 加密、校验和验证、密码保护的压缩) 也可用于 SPARQL-RL 文档。 必须实施安全/隐私协议,以反映 SPARQL-RL 规则集求值结果中信息的敏感程度。

SPARQL-RL 的安全考虑因素包括 RDF 数据 的安全考虑因素,以及 以下格式的安全考虑因素,例如 RDF Turtle。

E. 隐私注意事项

本节为非规范性内容。

SPARQL-RL 文档可以包含其他应用程序数据, 其中可能包含个人身份信息(PII) 或其他可能被视为敏感的信息。 建议发布包含此类信息的规则集的作者仔细 考虑发布此类信息的必要性和用途, 以及数据预计会被使用和可能被披露的地区 所适用的法规(例如 GDPR、 CCPA 和其他法规), 尤其应考虑访问这些数据是否需要授权措施。

F. 致谢

本节为非规范性内容。

以下人员在数据形状工作组的 规则任务组中为 SPARQL-RL 的制定作出了贡献: Robert David, David Habgood, Livio Robaldo, Ognjen Savkovic, Andy Seaborne, Simon Steyskal, 和 Ted Thibodeau Jr.

数据形状工作组的成员包括 @@。

G. 索引

G.1 本规范定义的 术语

G.2 通过引用定义的术语

H. 问题摘要

I. 参考文献

I.1 规范性引用文献

[EBNF-NOTATION]
EBNF 表示法。Tim Bray; Jean Paoli; Michael Sperberg-McQueen; Eve Maler; François Yergeau 等。W3C。W3C 推荐标准。URL:https://www.w3.org/TR/xml/#sec-notation
[I18N-GLOSSARY]
国际化术语表。 Richard Ishida; Addison Phillips。W3C。2024 年 10 月 17 日。W3C 工作组说明。URL:https://www.w3.org/TR/i18n-glossary/
[RDF12-CONCEPTS]
RDF 1.2 概念与抽象数据 模型。Andy Seaborne; Gregg Kellogg; Olaf Hartig; Pierre-Antoine Champin。W3C。2026 年 4 月 7 日。 W3C 候选推荐标准。URL:https://www.w3.org/TR/rdf12-concepts/
[RDF12-TURTLE]
RDF 1.2 Turtle。Gregg Kellogg; Andy Seaborne; Dominik Tomaszuk。W3C。2026 年 10 月 23 日。W3C 工作草案。URL:https://www.w3.org/TR/rdf12-turtle/
[RFC2119]
用于 RFC 中表示 要求级别的关键词。S. Bradner。IETF。1997 年 3 月。当前最佳实践。URL:https://www.rfc-editor.org/info/rfc2119/
[RFC3629]
UTF-8,ISO 10646 的一种转换格式。F. Yergeau。IETF。2003 年 11 月。互联网标准。URL:https://www.rfc-editor.org/info/rfc3629/
[RFC3986]
统一资源标识符(URI):通用 语法。T. Berners-Lee; R. Fielding; L. Masinter。IETF。2005 年 1 月。互联网 标准。URL:https://www.rfc-editor.org/info/rfc3986/
[RFC3987]
国际化资源标识符 (IRI)。M. Duerst; M. Suignard。IETF。2005 年 1 月。提议标准。URL:https://www.rfc-editor.org/info/rfc3987/
[RFC6906]
'profile' 链接关系 类型。E. Wilde。IETF。2013 年 3 月。资料性文档。URL:https://www.rfc-editor.org/info/rfc6906/
[RFC8174]
RFC 2119 关键词中大写与小写的歧义。B. Leiba。IETF。2017 年 5 月。当前最佳实践。URL:https://www.rfc-editor.org/info/rfc8174/
[SPARQL12-QUERY]
SPARQL 1.2 查询语言。Olaf Hartig; Andy Seaborne; Ruben Taelman; Gregory Williams; Thomas Pellissier Tanon。W3C。2026 年 10 月 25 日。 W3C 工作草案。URL:https://www.w3.org/TR/sparql12-query/
[UNICODE]
Unicode 标准。Unicode 联盟。URL:https://www.unicode.org/versions/latest/

I.2 资料性引用文献

[RDF12-SEMANTICS]
RDF 1.2 语义。Peter Patel-Schneider; Dörthe Arndt; Enrico Franconi。W3C。2026 年 9 月 24 日。候选推荐标准草案。URL:https://www.w3.org/TR/rdf12-semantics/