Uche Ogbuji 繼續討論一種 RDF 查詢語言,該語言比他迄今為止所討論的基本 API 要更為復雜得多。它是下幾篇問題跟蹤器專欄文章中要建立的中間件的基礎。
到現在為止,在對人們可能怎樣使用和查詢問題跟蹤器 RDF 元數據的簡短討論中,我們使用了簡單而基本的查詢 API。現在我們轉向一種更為強大的查詢語言。這將有助於編寫更清晰的中間件代碼,也將提供合並巨型模型(比如好幾篇專欄文章以前所演示的 WordNet 模型)所需的性能。
Versa:RDF 查詢語言
Versa 是一種開放 RDF 查詢語言,它基於開發人員對能夠在其它應用程序中使用的 RDF 工具這種典型的需要。它是一種不僅僅具有 RDF 世界觀的產品。Versa 著重於 RDF 模型的節點和圓弧而不是將其看作三元部分的集合。它提供核心數據模型以及一組非常豐富的用於靈活查詢的函數和原語。因為 Versa 大量使用函數,它有時給人一種類 LISP 的感覺。Versa 也提供如完全的布爾邏輯和集合運算、傳遞運算、聚合、子串匹配以及其它核心數據類型操作之類的功能,許多其它 RDF 查詢系統都不具有這些功能。我是最初的 Versa 規范的作者之一。
Versa 的核心是匹配模型圖中模式的遍歷表達式。下面是遍歷表達式的一個示例。
all() - rdf:type -> *
all() 函數返回模型中所有資源的集合。根據 RDF 模型的約定畫法,這意味著它返回所有的橢圓和圓弧(但不返回矩形)。 - 和 -> 記號形成遍歷運算符,它表示您希望沿著來自每個資源的特定圓弧遍歷。在這個例子中,該圓弧是 rdf:type 。 * 表示您想要該遍歷的所有端點。實際上,該遍歷表達式返回具有 rdf:type 謂詞的所有語句的對象。
作為一個示例,讓我們來看前一篇專欄文章中的樣本 RDF 問題跟蹤器實例,下面的清單 1 重復了該實例。
清單 1:
<?XML version='1.0'?>
<!DOCTYPE rdf:RDF [
<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#">
<!ENTITY daml "http://www.daml.org/2001/03/daml+oil#">
<!ENTITY dc "http://purl.org/dc/elements/1.1/">
<!ENTITY foaf "http://XMLns.com/foaf/0.1/">
<!ENTITY it "http://rdfinference.org/schemata/issue-tracker/">
<!ENTITY rit "http://rdfinference.org/ril/issue-tracker/">
]>
<rdf:RDF
XMLns:rdf="&rdf;"
XMLns:rdfs="&rdfs;"
XMLns:daml="&daml;"
XMLns:rit="&rit;"
XMLns:it="⁢"
XMLns:dc="&dc;"
XMLns:foaf="&foaf;"
XMLns="⁢"
>
<rdf:Description rdf:about='http://rdfinference.org/ril/ril-20010502'>
<issue rdf:resource='&rit;i2001030423'/>
<issue rdf:resource='&rit;i2001042003'/>
</rdf:Description>
<Issue rdf:about='&rit;i2001030423'>
<dc:title>Unnecessary abbreviation</dc:title>
<dc:creator rdf:resource='mailto:Alexandre.Fayolle@logilab.fr'/>
<dc:description>Is the abbreviation of rdf:type predicates needed?</dc:description>
<dc:date>2001-03-04</dc:date>
<comment rdf:parseType="Resource">
<dc:creator rdf:resource='mailto:Alexandre.Fayolle@logilab.fr'/>
<dc:description>The abbreviation in listing 8 doesn't seem needed.</dc:description>
</comment>
<action rdf:parseType="Resource">
<dc:description>Organize a vote on this topic</dc:description>
<it:assignee rdf:resource='mailto:uche.ogbuji@fourthought.com'/>
</action>
</Issue>
<Issue rdf:about='&rit;i2001042003'>
<dc:title>Inconsistent versioning</dc:title>
<dc:creator rdf:resource='mailto:Nicolas.Chauvat@logilab.fr'/>
<dc:description>RIL versioning is unclear (mix of 0.1, 0/1, 0.2 and 0/2)</dc:description>
<dc:date>2001-04-20</dc:date>
<action rdf:parseType="Resource">
<dc:description>Correct all to use the "0/1" form in the next draft.</dc:description>
<it:assignee rdf:resource='mailto:uche.ogbuji@fourthought.com'/>
</action>
</Issue>
<rdf:Description rdf:about='mailto:Alexandre.Fayolle@logilab.fr'>
<foaf:name>Alexandre Fayolle</foaf:name>
</rdf:Description>
<rdf:Description rdf:about='mailto:uche.ogbuji@fourthought.com'>
<foaf:name>Uche Ogbuji</foaf:name>
</rdf:Description>
<rdf:Description rdf:about='mailto:Nicolas.Chauvat@logilab.fr'>
<foaf:name>Nicolas Chauvat</foaf:name>
</rdf:Description>
</rdf:RDF>
圖 1 以圖形式演示了該樣本。
圖 1. RDF 問題跟蹤器樣本的圖模型
如果我們對該模型運行這一查詢,我們將得到一列資源(因為 rdf:type 語句的對象是資源)。如果我們正在使用一個可以有文字和資源對象的謂詞,結果將是一列文字和資源。要使用 4Suite 測試它(有關 4Suite 的詳細信息,請參閱以前的專欄文章),可以將清單 1 復制到文件 issues.rdf,然後在命令行中執行用紅色突出顯示的命令:
$4versa --rdf-file=issues.rdf "all() - rdf:type -> *"
::: Using cDomlette
Executing Query:
all() - rdf:type -> *
With nsMapping of:
vtrav --> http://rdfinference.org/versa/0/2/traverse/
xml --> http://www.w3.org/XML/1998/namespace
vsort --> http://rdfinference.org/versa/0/2/sort/
rdfs --> http://www.w3.org/2000/01/rdf-schema#
rit --> http://rdfinference.org/schemata/issue-tracker/
it --> http://rdfinference.org/schemata/issue-tracker/
rdf --> http://www.w3.org/1999/02/22-rdf-syntax-ns#
foaf --> http://XMLns.com/foaf/0.1/
versa --> http://rdfinference.org/versa/0/2/
None --> http://rdfinference.org/schemata/issue-tracker/
daml --> http://www.daml.org/2001/03/daml+oil#
<List>
<Resource>http://rdfinference.org/schemata/issue-tracker/Issue/<Resource>
<Resource>http://rdfinference.org/schemata/issue-tracker/Issue</Resource>
</List>
以簡單 XML 形式表示的產生的資源列表是用粗體顯示的。該命令的其它輸出只是為您提供一些信息。它回顯正在被執行的 Versa 查詢,並顯示引擎知道的名稱空間聲明。為了方便用戶,4versa 自動抓取源文件根元素內的所有名稱空間聲明。
細述遍歷
通常,遍歷表達式的格式如下:
list-expression -
list-expression ->
boolean-expression
列表表達式是返回一列資源或可以被轉換成一列資源的結果的任何表達式。因此,任何返回單個資源( rdf:type )的表達式都會被轉換成具有單個輸入項的列表類型。您已經看到過縮寫成如 rdf:type 的形式和稱為限定名稱(或 QNames )形式的 RDF URI。通過將第一部分擴展成 URI 庫(例如 rdf 變成 http://www.w3.org/1999/02/22-rdf-syntax-ns# )然後同第二部分連接,將它們轉換成完整的 URI。這樣, rdf:type 就變成了 http://www.w3.org/1999/02/22-rdf-syntax-ns#type 。Versa 也允許您以完整、冗長和詳細地方式拼寫出 URI,這意味著您可以直接編寫 @"http://www.w3.org/1999/02/22-rdf-syntax-ns#type" 。例如:
all() - @"http://www.w3.org/1999/02/22-rdf-syntax-ns#type" -> *
遍歷表達式的第三部分是布爾表達式。我已經向您演示了如何使用 * 選擇所有對象。您也可以對結果做更多的選擇。例如,要獲取模型中資源的所有日期特性,您可以使用:
all() - dc:date -> *
它產生:
<List>
<String>2001-03-04</String>
<String>2001-04-20</String>
</List>
要選擇特定日期,您可以編寫:
all() - dc:date -> eq("2001-04-20")
eq 函數將參數同上下文比較,如果它們相同,就返回 true。Versa 中上下文的思想類似於 XPath 中上下文的思想,但是更簡單。在 Versa 中,上下文是一個在對表達式求值時考慮的單一值。可以使用點符號直接訪問上下文。也可以使用 eq 函數比較兩個顯式參數,因此上面的內容可以按如下編寫:
all() - dc:date -> eq(., "2001-04-20")
在遍歷表達式的第三部分中,上下文是第一和第二部分的部分結果之一。例如,在上面,將每個對象都同“2001-04-20”比較,最後的結果是比較值為 true 的對象列表,在這個示例中:
<List>
<String>2001-04-20</String>
</List>
圖 2 演示了遍歷表達式的某些工作。
圖 2. 遍歷表達式的工作演示
這一看起來明顯的查詢在確定某個特定值是否是模型時十分有用。例如,如果您在上面的示例中用“2001-03-15”替換“2002-04-20”,那麼結果將是一個空列表。當然您可以使用遍歷表達式做更多的事情。例如,要檢索三月份所有日期的資源,您可以編寫:
all() - dc:date -> contains("-03-")
它產生:
<List>
<String>2001-03-04</String>
</List>
向後遍歷
獲得具有特定日期的 資源 可能更有用。要做到這一點,您需要向後操作,從 dc:date 圓弧所對應的日期到主題資源。Versa 以向後遍歷的形式提供這一操作。例如:
"2001-03-04" <- dc:date - *
返回所有日期為“2001-03-04”的資源:
<List>
<Resource>http://rdfinference.org/ril/issue-tracker/i2001030423</Resource>
</List>
向後遍歷的形式是:
list-expression <-
list-expression -
boolean-expression
其工作方式與向前遍歷十分類似。這兩種遍歷都可以鏈接,這樣就可以獲取日期為“2001-03-04”的所有資源的標題:
("2001-03-04" <- dc:date - *) - dc:title -> *
它產生:
<List>
<String>Unnecessary abbreviation</String>
</List>
分配財富
到目前為止,所有查詢都返回單一值。通常,您可能希望一次返回多個值。Versa 使用列表操作實現這一點,列表操作運行在遍歷表達式的結果之上。處理列表的一個常用函數是 distribute ,它對列表中的每項使用一個或多個表達式。結果是列表的一個列表。可以使用如 list(rit:i2001030423, rit:i2001042003) 這樣的表達式在 Versa 中直接表達列表,該表達式是兩個資源的列表。下面的表達式獲得這些問題中每個問題的標題和日期:
distribute(list(rit:i2001030423, rit:i2001042003), ".-dc:title->*", ".-dc:date->*")
它產生列表的列表:
<List>
<List>
<List>
<String>Unnecessary abbreviation</String>
</List>
<List>
<String>2001-03-04</String>
</List>
</List>
<List>
<List>
<String>Inconsistent versioning</String>
</List>
<List>
<String>2001-04-20</String>
</List>
</List>
</List>
distribute 函數的第一個參數是一個列表。依次獲取列表中的每一項。第二個和後續的參數為字符串,它們被當作子查詢。使用上下文的當前列表項對它們進行動態求值(如同前面所討論的一樣,通過使用點來引用)。圖 3 演示了該查詢是如何工作的。
圖 3. distribute 的工作演示
可以對遍歷表達式的結果(它們是列表)使用該技術。再舉最後一個例子,我將解釋如何使用 Versa 中的一個特殊快捷函數。 type 函數檢索給定 RDF 類型(就象用 rdf:type 謂詞表達那樣)的所有資源。要獲取所有已經提交問題的人的標識和姓名,可以編寫:
distribute(type(it:Issue)-dc:creator->*, ".", ".-foaf:name->*")
請注意,您可以在子表達式中直接使用上下文,而無須使之成為另一個表達式的一部分。結果是:
<List>
<List>
<Resource>mailto:Nicolas.Chauvat@logilab.fr</Resource>
<List>
<String>Nicolas Chauvat</String>
</List>
</List>
<List>
<Resource>mailto:Alexandre.Fayolle@logilab.fr</Resource>
<List>
<String>Alexandre Fayolle</String>
</List>
</List>
</List>
這裡有一個棘手的細節。 .-foaf:name->* 子表達式產生的字符串在列表中,但由 . 子表達式產生的資源卻不在其中。這是因為遍歷操作符總返回列表,即使結果中只有一個或沒有項時也是如此。由於我們知道在我們的模型中,我們僅僅期望每個人的資源只有一個單一名稱,所以我們無論如何總是忽略列表的最低一級。Versa 提供數據轉換函數,其中之一是 string 函數,它將其參數轉換成字符串。通過獲取列表的第一(或唯一的)項的字符串值來轉換列表。因此,
distribute(type(it:Issue)-dc:creator->*, ".", "string(.-foaf:name->*)")
消除了遍歷子表達式周圍的無關列表並產生以下結果:
<List>
<List>
<Resource>mailto:Nicolas.Chauvat@logilab.fr</Resource>
<String>Nicolas Chauvat</String>
</List>
<List>
<Resource>mailto:Alexandre.Fayolle@logilab.fr</Resource>
<String>Alexandre Fayolle</String>
</List>
</List>
結束語
在本文中,我解釋了 Versa 的基礎知識。如果您掌握了它,那麼您立即可以使用它來高效地工作。Versa 有多得多的功能,但是它們大部分是以專門的函數的形式出現,通過使用它們,您將迅速獲得經驗。參考資料一節列舉了更多關於 Versa 的參考資料。在下一篇專欄文章裡,我將解釋如何使 Versa 滿足到目前為止在本系列中討論的所有查詢需要。