This document introduces AllegroGraph. It assumes that you are somewhat familiar with RDF (Resource Description Framework), RDFS (RDF Schema), and OWL (Web Ontology Language). If you want to learn more about RDF, RDFS and OWL, see Suggested Reading for recommended introductory texts.
The AllegroGraph Tutorial
AllegroGraph only works with Allegro CL 8.1 or higher. Please contact [email protected] to obtain a copy of Allegro CL 8.1 and AllegroGraph.
This tutorial shows AllegroGraph in action. If you want to work along, you will need to download and install AllegroGraph before executing the examples shown below.
The code examples used in this tutorial are installed along with AllegroGraph, and reside in the agraph/tutorial-files/ subdirectory of the Allegro directory. On Windows, the Allegro directory is usually c:/acl82.
To make it easier to follow along, we suggest that you open Agraph-tutorial.lisp now in your IDE of choice. The examples that follow assume that the files are located in agraph/ subdirectory of the Allegro CL installation directory. Note that the triple IDs that are returned as you work through the tutorial may differ from those displayed in this document; there is no need to be concerned about this.
Starting Allegro CL and loading AllegroGraph
AllegroGraph works best with mlisp (Modern case-sensitive Lisp). It also supports alisp (ANSI case-insensitive Lisp). All of the examples in this document are from mlisp. To use AllegroGraph, start Allegro CL and evaluate the following two forms:
;; load AllegroGraph
(require :agraph)
;; Switch to the correct package
(in-package :db.agraph.user)
;; turn on the convenience !-reader
(enable-!-reader)
If the (require :agraph) fails, please make sure the installation was performed correctly.
Creating or Opening a triple-store
The first thing we need to do is to create a triple-store. Most of the functions described below will not work without an open triple-store. So when you are experimenting always have a triple-store open.
The function create-triple-store creates a new triple-store and opens it. If you use the triple-store name "ag-test", then AllegroGraph will create a new directory named ag-test
in your current directory (use the top-level command :pwd if you want to see where this is). All of this triple-store's data will be placed in this new directory.
1
> :pwd
Lisp's current working directory is "/Users/gwking/"
*default-pathname-defaults* is #P"/Users/gwking/"
> (create-triple-store "ag-test")
#<triple-db /Users/gwking/ag-test, open @ #x130f4f72>
We will use ag-test
throughout this document but you should of course feel free to use your own choice of name! Once a triple-store exists, you can use open-triple-store to reopen it.
Namespaces and the reader macro for nodes
When working in Lisp with AllegroGraph, you will spend a lot of time inspecting triples and trying to find triples by typing in URIs. To save you typing the namespace part of a URI over and over again, we borrowed the Wilbur reader macro. To see ! in action, let's register a namespace:
> (register-namespace "ex" "http://www.franz.com/things#"
:errorp nil)
"http://www.franz.com/things#"
AllegroGraph already registered several common RDF(S) and OWL namespaces for you. You can see them using display-namespaces:
> (display-namespaces)
rdf => http://www.w3.org/1999/02/22-rdf-syntax-ns#
xsd => http://www.w3.org/2001/XMLSchema#
ex => http://www.franz.com/things#
rdfs => http://www.w3.org/2000/01/rdf-schema#
fn => http://www.w3.org/2005/xpath-functions#
err => http://www.w3.org/2005/xqt-errors#
owl => http://www.w3.org/2002/07/owl#
xs => http://www.w3.org/2001/XMLSchema#
Now we are ready to experiment with the !-reader macro.
! expands namespace prefixes and understands both quotation marks and Unicode. For example, the following two nodes are the same:
> !ex:Dog
!ex:Dog
> !<http://www.franz.com/things#Dog>
!<http://www.franz.com/things#Dog>
> (part= !ex:Dog !<http://www.franz.com/things#Dog>)
t
;; the literal string "Dog"
> !"Dog"
Evaluating these forms puts the following nodes into the string dictionary:
"http://www.franz.com/things#Dog"
"Dog"
The function part->string reverses the mapping from node to the URI string. In this case, use the value that lisp returned earlier for !ex:Dog.
> (part->string !ex:Dog)
"<http://www.franz.com/things#Dog>"
When you are developing your application in the listener, you may not want to look at the entire URI. The function part->concise is like part->string but presents a best-guess abbreviation of the URI.
> (part->concise !ex:Cat)
"ex:Cat"
part->concise will return just the string when called on a UPI that refers to a literal.
> (part->concise !"A string")
"A string"
AllegroGraph Notation
Before we go much further, we'll describe the notation that AllegroGraph uses to represent resources and literals as strings, future-parts and Unique Part Identifiers (UPIs).
AllegroGraph strikes a balance between displaying complete but lengthy URIs and concise but meaningless abbreviations. The output format also depends on whether it is displaying a string representation of a UPI, a future-part or a bare UPI. For strings, AllegroGraph adopts the N-Triples notation for URIs and provides several different output formats for you to choose from. These are:
:ntriple - ASCII escaping (i.e., non 7-bit ASCII and control characters are printed in N-Triples format) plus N-Triple syntax for resources, blanks and literals
:long - N-Triple syntax for resources, blanks and literals. Prints characters using Unicode.
:concise - ASCII escaping, no extra literal information (i.e., any language and type information is suppressed in the output). If a namespace mapping is available, it will be used.
:terse - the string contents of the UPI without additional type or language data for literals (as in :concise); namespace information will be suppressed.
Future-parts are displayed using namespace plus local name (if a namespace mapping is available). Finally, depended on whether or not you have used the function enable-print-decoded, UPIs will display themselves as length 12 octet vectors or in the terse string notation surrounded by curly brackets.
The following are thus all representative of the same RDF literal "Dürst" (note the umlaut over the u
):
- as an undecoded UPI : #(5 0 0 0 0 116 115 114 60 195 68 7)
- as a decoded UPI : {Dürst}
- as a future-part : !"Dürst"
- String in N-triples format : "\"D\u00FCrst\""
- String in long format : "\"Dürst\""
- String in concise format : "D\u00FCrst"
- String in terse format : "Dürst"
Here is another brief example:
> (enable-print-decoded nil)
nil
> (upi !rdf:type) ;no decoding
#(232 179 38 248 194 24 210 86 213 0 183 0)
> (enable-print-decoded t)
t
> (setf u (upi !rdf:type)) ;terse format
{type}
> (part->string u :format :ntriples)
"<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
> (part->string u :format :long) ;just like :ntriples in this case
"<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
> (part->string u :format :concise) ;concise includes namespace
"rdf:type"
> (part->string u :format :terse) ;terse does not
"type"
You'll see many examples of the different formats as you work through this tutorial. When in doubt, experiment!
Creating a triple programmatically
Now that we know how to enter nodes we will add some triples programmatically. (Note: The triple id numbers returned below are session dependent, and you may get different numbers in your experiment. Make sure to use a number returned by one of the expressions as you continue this tutorial.)
> (add-triple !ex:Mammal !rdf:type !owl:Class)
1
> (add-triple !ex:Dog !rdfs:subClassOf !ex:Mammal)
2
> (add-triple !ex:Fido !rdf:type !ex:Dog)
3
> (add-triple !ex:Fido !ex:has-owner !ex:John)
4
> (add-triple !ex:Company !rdf:type !owl:Class)
5
> (add-triple !ex:CommercialCompany !rdfs:subClassOf !ex:Company)
6
> (add-triple !ex:Franz !rdf:type !owl:CommercialCompany)
7
> (add-triple !ex:John !ex:works-at !ex:Franz)
8
> (add-triple !ex:Franz !ex:has-product !ex:Agraph)
9
The numbers that are returned by add-triple are the unique triple-ids of the just added triple. We can use these numbers to look at a triple. Let's look at triple ID 4.
> (setf tr (get-triple-by-id 4))
<Fido has-owner John>
A triple data structure is returned. 2 This is a vector of 56 octets representing the four (!) parts of the triple and its triple ID. The parts are the subject, predicate, object and graph. You can use accessor functions subject, predicate, object, graph and triple-id to get at a triple's sub-structure.
> (triple-id tr)
4
> (subject tr)
{Fido}
The four nodes of a triple are represented as Unique Part Identifiers (or UPIs). These are either 12-byte hashed representations of URIs and literals or directly encoded values. See the AllegroGraph Introduction and the reference for more information. To see the triple in its native representation, we can turn off enable-print-decoded:
> (enable-print-decoded nil)
nil
> tr
#(78 134 60 220 246 40 208 148 75 219 198 0 119 141 131 175 192 18 161
94 17 1 23 0 202 224 162 157 147 62 130 139 128 204 27 0 0 0 0 0 167
99 152 67 0 0 0 31 4 0 0 0 18 40 2 16)
;; let's turn it back on for now
> (enable-print-decoded t)
t
Notes on add-triple
The function add-triple takes either strings, UPIs, or future-parts. If the argument is a UPI AllegroGraph assumes that it is a valid 3 . It is slightly more complicated to understand what happens when you use strings as arguments. AllegroGraph cannot always see from a string whether you want it to be a resource or a literal. Therefore we have chosen the convention that the strings should follow the N-Triples syntax. The reader macro will do this for you automatically
> !ex:John
!ex:John
> !"john"
!"john"
so (add-triple !ex:John !rdf:comment !"john")
will do the right thing.
Suppose, however, you need to create resource and literal strings programmatically. For that we have created the convenience functions resource and literal:
> (resource "http://www.franz.com/simple#Peter")
!<http://www.franz.com/simple#Peter>
> (literal "Peter")
!"Peter"
> (let ((str1 "http://www.franz.com/simple#Peter")
(str2 "Peter"))
(setf triple-id
(add-triple (resource str1) !rdf:comment (literal str2))))
10
> (print-triple (get-triple-by-id triple-id) :format :concise)
<10: http://www.franz.com/simple#Peter rdf:comment Peter>
#(200 74 120 93 224 212 32 42 151 60 ...)
> (close-triple-store)
nil
Printing a triple
To print out a triple, use the print-triple function (see also "Querying for triples" below). print-triple
can print the triple in concise, long or N-Triples format. The default is controlled by the AllegroGraph property :default-print-triple-format.
> (print-triple tr :format :concise)
<4: ex:Fido ex:has-owner ex:John> ;printed
<Fido has-owner John> ;returned
> (dolist (e (get-triples-list))
(print-triple e :format :concise))
<1: ex:Mammal rdf:type owl:Class>
<2: ex:Dog rdfs:subClassOf ex:Mammal>
<3: ex:Fido rdf:type ex:Dog>
<4: ex:Fido ex:has-owner ex:John>
<5: ex:Company rdf:type owl:Class>
<6: ex:CommercialCompany rdfs:subClassOf ex:Company>
<7: ex:Franz rdf:type owl:CommercialCompany>
<8: ex:John ex:works-at ex:Franz>
<9: ex:Franz ex:has-product ex:Agraph>
Note that the order of the triples returned from get-triples-list is unspecified so you may see a different result order. Developers desiring to see the entire triple URI can set the keyword :format to :long or :ntriples.
> (print-triple tr :format :long)
<http://www.franz.com/things#Fido> <http://www.franz.com/things#has-owner> <http://www.franz.com/things#John> .
<Fido has-owner John>
> (print-triple tr :format :ntriple)
<http://www.franz.com/things#Fido> <http://www.franz.com/things#has-owner> <http://www.franz.com/things#John> .
<Fido has-owner John>
Printing nodes again
When developing, it is sometimes useful to print everything related to a particular node, for n levels deep. pprint-subject and pprint-object will do that for you.
> (pprint-subject !ex:Fido :format :concise)
ex:Fido rdf:type ex:Dog
ex:Fido ex:has-owner ex:John
> (pprint-subject !ex:Fido :maximum-depth 3 :format :concise)
ex:Fido rdf:type ex:Dog
ex:Dog rdfs:subClassOf ex:Mammal
ex:Mammal rdf:type owl:Class
ex:Fido ex:has-owner ex:John
ex:John ex:works-at ex:Franz
ex:Franz ex:has-product ex:Agraph
ex:Franz rdf:type owl:CommercialCompany
> (pprint-object !ex:Mammal :maximum-depth 2 :format :concise)
ex:Dog rdfs:subClassOf ex:Mammal
ex:Fido rdf:type ex:Dog
Querying for triples
Getting the triples back is easy. The function (get-triples-list) can return all the triples in the triple-store; so don't issue it casually with a large triple-store. Variations of (get-triples-list), described later, allow you to select a specific portion of the triples in the triple-store. The following generates a list with all the triples in the triple-store.
> (enable-print-decoded nil)
nil
> (get-triples-list :limit nil)
(#(58 199 69 20 59 219 255 76 6 52 ...)
#(95 10 54 142 106 57 232 84 206 188 ...)
#(78 134 60 220 246 40 208 148 75 219 ...)
#(78 134 60 220 246 40 208 148 75 219 ...)
#(224 55 238 14 54 24 123 47 0 107 ...)
#(119 170 85 50 93 233 51 22 180 89 ...)
#(140 251 141 206 154 143 61 27 140 58 ...)
#(202 224 162 157 147 62 130 139 128 204 ...)
#(140 251 141 206 154 143 61 27 140 58 ...))
(Note that the order of triples returned is unspecified so the exact result may differ.) You can query the triple-store whether or not it has been indexed (unindexed queries will be much slower). Once the triple-store is indexed with index-all-triples, any combination of specific values of s, p, o and/or g will result in a direct lookup from disk or cache. get-triples-list will take any combination of s, p, o, and g (nil is used as a wildcard and means to return all possible values for the target). The function print-triples lets you print out a list of triples more easily. Like print-triple, it takes a format keyword argument that controls whether the output is in :concise, :long or :ntriple format. The default is controlled by the variable *default-print-triple-format*.
> (print-triples (get-triples-list :o !ex:John) :format :concise)
<4: ex:Fido ex:has-owner ex:John>
> (print-triples (get-triples-list :s !ex:Franz :p !rdf:type) :format :concise)
<7: ex:Franz rdf:type owl:CommercialCompany>
> (print-triples (get-triples-list :s !ex:John) :format :concise)
<8: ex:John ex:works-at ex:Franz>
A cursor version
get-triples-list is built on the cursor based query function get-triples. When you call get-triples
, the first return value will be a cursor that you can use to iterate over the results. The second return value is a query plan which can be safely ignored for now.
> (get-triples :s !ex:Franz)
#<db.agraph::filtered-cursor
#<db.agraph::row-cursor
#<db.agraph::triple-record-file @ #x10026d8032> 1 [1 - 9] @ #x1002c1daf2>
@ #x1002c1dcd2>
Here is how we might implement a variant of get-triples-list
that uses optional arguments instead of keyword ones:
(defun my-get-triples-list (&optional s p o g)
(loop with cursor = (get-triples :s s :p p :o o :g g)
while (cursor-next-p cursor) collect
(copy-triple (cursor-next-row cursor))))
Note the use of copy-triple. The cursor reuses the same vector over and over again; if you don't make a copy, you'll probably be surprised at the results!
Loading triples from a file
We support both N-Triples format and RDF/XML format. If you need to load other formats (like Turtle) we suggest you use the excellent free tool rapper from http://librdf.org/raptor/rapper.html to create either an N-Triples or RDF/XML file that AllegroGraph can read.
Important note: When AllegroGraph reads an N-Triples or RDF/XML file it won't immediately index the triples. That doesn't mean that you cannot do queries over the triples as soon as a file is read in. It does mean that any queries you do will be slower. If you can afford the luxury of doing a bulk insert then we suggest that you first read in all of your data and only then start the indexing. After you have done the indexing you can again load in triples from other files (or create them programmatically). You can use the function indexing-needed-p to see if your triple-store has any unindexed triples.
;; alternate syntax for specifying a triple-store's directory
> (create-triple-store "test" :directory "temp/")
Load in the file,
> (load-ntriples "sys:agraph;tutorial-files;wilburwine.ntriples")
2012
{default-graph}
This reads the file, creates triples, and then returns the total number of triples created. Note, instead of load-ntriples you also could have used load-rdf/xml on "sys:agraph;tutorial-files;wilburwine.rdf". You can see the total number of triples in the store using the triple-count function.
> (triple-count)
2012
The file is not indexed yet, but all the triples are already available for query.
> (indexing-needed-p)
t
> (get-triples-list)
(<wine type Ontology> <wine comment An example OWL ontology>
<wine type Ontology> <wine priorVersion wine> <wine imports food>
<wine comment Derived from the DAML Wine ontology at http://ontolingua.stanford.edu/doc/chimaera/ontologies/wines.daml Substantially changed, in particular the Region based relations. >
<wine label Wine Ontology> <Wine type Class>
<Wine subClassOf PotableLiquid> <_anon:1 type Restriction>
<_anon:1 onProperty hasMaker> <_anon:1 cardinality 1>
<Wine subClassOf _anon:1> <_anon:2 type Restriction>
<_anon:2 onProperty hasMaker> <_anon:2 allValuesFrom Winery>
<Wine subClassOf _anon:2> <_anon:3 type Restriction>
<_anon:3 onProperty madeFromGrape> <_anon:3 minCardinality 1>
<Wine subClassOf _anon:3> <_anon:4 type Restriction>
<_anon:4 onProperty hasSugar> <_anon:4 cardinality 1>
<Wine subClassOf _anon:4> <_anon:5 type Restriction>
<_anon:5 onProperty hasFlavor> <_anon:5 cardinality 1>
<Wine subClassOf _anon:5> <_anon:6 type Restriction>
<_anon:6 onProperty hasBody> <_anon:6 cardinality 1>
<Wine subClassOf _anon:6> <_anon:7 type Restriction>
<_anon:7 onProperty hasColor> <_anon:7 cardinality 1>
<Wine subClassOf _anon:7> <_anon:8 type Restriction>
<_anon:8 onProperty locatedIn> <_anon:8 someValuesFrom Region>
<Wine subClassOf _anon:8> <Wine label wine> <Wine label vin>
<Vintage type Class> <_anon:9 type Restriction>
<_anon:9 onProperty hasVintageYear> <_anon:9 cardinality 1>
<Vintage subClassOf _anon:9> <WineGrape type Class>
<WineGrape subClassOf Grape>)
#<filtered-cursor
#<row-cursor #<triple-record-file @ #x13481b3a> 51 [1 - 2012] @
#x13699b82>
@ #x13699cb2>
> (length *)
50
And remember you can always tell the printer to not interpret triples for you:
> (enable-print-decoded nil)
nil
> (get-triples-list)
(#(22 248 167 148 97 40 45 66 55 197 211 0 232 179 38 248 194 24 210 86
213 0 183 0 197 86 96 93 164 128 215 161 181 145 139 0 0 0 0 0 24
124 5 85 0 0 0 31 1 0 0 0 0 0 0 0)
#(22 248 167 148 97 40 45 66 55 197 211 0 137 150 27 193 14 75 23 210
55 170 134 0 213 222 172 190 107 40 26 47 156 193 78 1 0 0 0 0 24
124 5 85 0 0 0 31 2 0 0 0 0 0 0 0)
...)
#<db.agraph::row-cursor
#<db.agraph::triple-record-file @ #x100257a2c2> 51 [1 - 2012]
@ #x1002b26392>
(Note that the order of triples returned is unspecified so the exact result may differ.) Only 50 triples appear in the result list because get-triples-list limits its output using the variable *get-triples-list-limit* (see also *print-triples-list-limit*). If we wanted to get all of the triples we could either set *get-triples-list-limit* to nil or use the :limit keyword argument. (Note that it is much more efficient to use triple-count to see how many triples are in a triple-store, calling length on get-triples-list
is not generally a good practice.)
> (length (get-triples-list :limit nil))
2012
Let us retrieve one node. We first register a namespace to save us some typing
> (register-namespace
"guide" "http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#")
> (part->string !guide:Wine)
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>"
> (get-triples-list :s !guide:Wine)
(<8: guide:Wine rdf:type owl:Class>
<9: guide:Wine rdfs:subClassOf PotableLiquid>
<13: guide:Wine rdfs:subClassOf _anon:1>
...)
How many did we find?
> (length *)
43
Next we index the main triple file, (Note that the actual messages printed during indexing may differ from those shown in this tutorial and depend in part on the setting of (ag-property :verbose)
).
> (index-all-triples)
262 ;; this is the id of the indexing task created
;; and the value you get back will almost certainly
;; differ from the one shown here
and query the triple-store. Of course, we get the same triples again (though probably in a different order).
> (print-triples (get-triples :s !guide:Wine) :format :concise)
<triple 43: guide:Wine rdfs:label "guide"@fr default-graph>
<triple 42: guide:Wine rdfs:label "wine"@en default-graph>
<triple 8: guide:Wine rdf:type owl:Class default-graph>
<triple 53: guide:Wine rdf:type owl:Class default-graph>
<triple 202: guide:Wine rdf:type owl:Class default-graph>
<triple 330: guide:Wine rdf:type owl:Class default-graph>
<triple 340: guide:Wine rdf:type owl:Class default-graph>
<triple 419: guide:Wine rdf:type owl:Class default-graph>
<triple 512: guide:Wine rdf:type owl:Class default-graph>
<triple 526: guide:Wine rdf:type owl:Class default-graph>
....
Now let us register another namespace,
> (register-namespace "ex" "http://www.franz.com/things#"
:errorp nil)
"http://www.franz.com/things#"
and make the highly subjective statement that "Wine better-than beer".
> (add-triple !guide:Wine !ex:better-than !ex:beer)
2013
and now there are more triples with the subject "Wine" (case matters!):
> (count-cursor (get-triples :s !guide:Wine))
44
Deleting triples
AllegroGraph also supports deleting triples. The function delete-triples takes an spog pattern and will delete every triple that matches the pattern. So the following will delete every triple that starts with !guide:Wine and ends with !ex:beer. (by the way, the function takes either strings, UPIs or future-parts) :
> (delete-triples :s !guide:Wine :o !ex:beer)
1
It returns the number of triples deleted. For now, let's add back the triple so that we can use it below
> (add-triple !guide:Wine !ex:better-than !ex:beer)
2014
Reification
You might have noticed that, although we talk about a triple-store, our triples are actually quints. In AllegroGraph, every triple has a unique ID that you can use as a pointer to a first class object. This allows for very efficient reification (i.e., making statements about a triple).
RDF Reification
Normally in RDF you do the following for reification. Say you want to say that the statement "wine better-than beer" is nonsense.
> (let ((bn (new-blank-node)))
(add-triple bn !rdf:type !rdf:Statement)
(add-triple bn !rdf:subject !guide:wine)
(add-triple bn !rdf:predicate !ex:better-than)
(add-triple bn !rdf:object !ex:beer)
(add-triple bn !rdf:source !ex:Jans)
(add-triple bn !rdf:content !ex:Nonsense))
2020
So what did Jans think about the statement that "wine better-than beer"? Below is some Prolog code that will find "Nonsense". In later sections of this document, we will provide some pointers on how to use Prolog with AllegroGraph. For now this little example should be easy to understand intuitively. (Press "enter" after the first answer to get back to the command prompt.)
> (?- (q- ?st !rdf:type !rdf:Statement)
(q- ?st !rdf:subject !guide:wine)
(q- ?st !rdf:predicate !ex:better-than)
(q- ?st !rdf:object !ex:beer)
(q- ?st !rdf:source !ex:Jans)
(q- ?st !rdf:content ?x)
(lisp (print (part->string ?x))))
"<http://www.franz.com/things#Nonsense>"
?st = {_anon:460}
?x = {ex:Nonsense}
;; type [ENTER]
No.
Well, that was painful. Six triples to say something about one other triple. In AllegroGraph we therefore provide first-class triples.
Direct Reification - First-class triples
Each triple has an ID which can be used as the subject or object of other triples. This allows you to make statements about other statements in the triple-store.
For example, remember that the id number of the statement "wine better-than beer" was 2014. We'll use get-triple to bind the symbol tr
to our triple.
> (setq tr (get-triple
:s !guide:Wine :p !ex:better-than :o !ex:beer))
<2014: guide:Wine ex:better-than ex:beer>
> (print-triple tr :format :concise)
<triple 2014: guide:Wine ex:better-than ex:beer default-graph>
The most direct way to make a statement about the triple tr
is to simply add a triple that points to it. For example:
> (add-triple !ex:Jans !ex:classifies-as-nonsense
(value->upi (triple-id tr) :triple-id))
2021
> (print-triple (get-triple-by-id 2021) :format :concise)
<triple 2021: ex:Jans ex:classifies-as-nonsense 2014 default-graph>
Here, 2014 represents the ID of a triple, and we can directly use this ID in other triples. However, that is somewhat unfair in comparison to the classic reification. Here is a more analogous approach where we create a blank node to tie together our statement with the information what Jans thought:
> (let ((bn (new-blank-node)))
(add-triple bn !rdf:statementAbout
(value->upi (triple-id tr) :triple-id))
(add-triple bn !rdf:source !ex:Jans)
(add-triple bn !rdf:content !ex:Nonsense))
2024
So what did Jans think is nonsense (again hit Enter after the the ?ID line)?
> (?- (q- ?st !rdf:statementAbout ?id)
(q- ?st !rdf:source !ex:Jans)
(q- ?st !rdf:content !ex:Nonsense)
(lisp (print-triple (get-triple-by-id (upi->value ?id))
:format :concise)))
<2014: guide:Wine ex:better-than ex:beer>
?st = {_anon:460}
?id = {2014}
No.
Note that there are some differences between RDF reification and first-class triples; for example, the former explicitly does not make assertions about a particular statement, while the latter only makes assertions about a particular statement. Referencing triples also takes the contents of the store outside of standard RDF. For many applications, though, first-class triples are an efficient and direct approach to recording provenance and similar information.
Using Prolog (without the triple-store)
Prolog is a very convenient interface to the triple-store. If you are not familiar with it, please take a look at the Prolog tutorial included in the AllegroGraph distribution and at Franz's technical reference to Prolog in Allegro Common Lisp.
Using Prolog with the triple-store
The tutorial linked above provides a minimal introduction to Prolog. Now we will tie Prolog to AllegroGraph. Below are some basic operations. If your triple-store is still open, please close it first:
> (close-triple-store)
nil
Now do the following again (Note that as before the order of the results returned from get-triples-list and the actual messages printed during indexing may differ from those shown in this tutorial):
> (create-triple-store "ag-test")
#<db.agraph::triple-db ag-test/, open @ #x119884f2>
> (load-ntriples "sys:agraph;tutorial-files;wilburwine.ntriples")
2012
{default-graph}
> (get-triples-list :p !rdfs:subClassOf)
(<9: guide:Wine rdfs:subClassOf PotableLiquid>
<13: guide:Wine rdfs:subClassOf _anon:1>
<17: guide:Wine rdfs:subClassOf _anon:2>
<21: guide:Wine rdfs:subClassOf _anon:3> ...)
> (register-namespace "ex" "http://www.franz.com/things#"
:errorp nil)
"http://www.franz.com/things#"
> (register-namespace "guide"
"http://www.w3.org/TR/2003/WD-owl-guide-20030331/"
:errorp nil)
"http://www.w3.org/TR/2003/WD-owl-guide-20030331/"
(Notice that we used the :errorp
parameter to register-namespace
to make certain that it would not complain if we were changing a namespace mapping.)
> (add-triple !guide:wine !ex:better-than !ex:beer)
2013
> (let ((bn (new-blank-node)))
(add-triple
bn !rdf:statementAbout
(value->upi (add-triple !guide:wine !ex:better-than !ex:beer)
:triple-id))
(add-triple bn !rdf:source !ex:Jans)
(add-triple bn !rdf:content !ex:Nonsense))
2017
> (index-all-triples)
5
The main interface to prolog is the function q-. Let's try it out by asking for every possible binding for subject, predicate and object:
> (?- (q- ?x ?y ?z))
?x = {wine}
?y = {rdf:type}
?z = {owl:Ontology}
?x = {wine}
?y = {rdfs:comment}
?z = {"An example OWL ontology"}.
;; type #\. (period) to tell Prolog to stop
No.
...
This will get boring pretty quickly, you can stop it by typing a period (' . '). Let's try again with one argument to q-
that is bound. This will find all the direct subClassOf
relations in the triple-store.
4
> (?- (q- ?x !rdfs:subClassOf ?y))
?x = {guide:Wine}
?y = {PotableLiquid}
?x = {guide:Wine}
?y = {_anon:1}.
No.
Again, stop it by typing a period (' . ').
So what did Jans think is nonsense (at the pause press Enter)?
> (?- (q- ?st !rdf:statementAbout ?id)
(q- ?st !rdf:source !ex:Jans)
(q- ?st !rdf:content !ex:Nonsense)
(lisp (print-triple
(get-triple-by-id (upi->value ?id))
:format :concise)))
<triple 2015: guide:wine ex:better-than ex:beer default-graph>
?st = {_anon:461}
?id = {2015}
No.
When you get this tutorial you also get the file "kennedy-family.lisp". When you complete the current tutorial, you can evaluate the forms in "kennedy-family.lisp" to learn more about how Prolog interfaces to the triple-store. Do not load and compile this file all at once but execute it from top to bottom one form at a time so that you can view the output from each form interactively.
Using the select macro as a wrapper around Prolog
When you make a Prolog query you often are not interested in 12-byte numeric UPI vectors but you instead want to see actual string values. Because it is tedious to write (lisp (print (part->string ?x)))
repeatedly, there is a simple convenience wrapper around Prolog called select.
Here is how you use it (make sure that you have the wine triple-store open before evaluating the next form). 5
> (select (?x ?y ?z)
(q- ?x !rdfs:subClassOf ?y)
(q- ?y !rdfs:subClassOf ?z))
(("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/food#PotableLiquid>")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank33")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank44")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank51")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank58")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank64")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank70")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank76")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#DessertWine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>" "_:blank82")
("<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#LateHarvest>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/wine#Wine>"
"<http://www.w3.org/TR/2003/CR-owl-guide-20030818/food#PotableLiquid>")
...)
If you want to see how select
works, just use macroexpand on it.
In conclusion
This tutorial has introduced the simplest AllegroGraph operations:
- creating triple stores
- the !-reader
- adding triples one at a time using add-triple or in bulk with load-ntriples
- querying with get-triples and get-triples-list
- printing triples with print-triple, print-triples, pprint-subject and pprint-object.
- triple deletion
- reification
- Prolog and select
To continue, see the many tutorials (linked in the navigation bar at the top of this page) and the AllegroGraph Learning Center.
Footnotes
- Note that create-triple-store will automatically overwrite any AllegroGraph files when it creates the new triple-store. ↩
-
It is printed readably because enable-print-decoded has been set to true; a little further along in the tutorial, we will show you what happens if
enable-print-decoded
is nil ↩ - I.e., that the string it represents is already interned in the triple-store's string dictionary. ↩
- The q- functor queries the triple-store's inner-triple-store and therefore will not, in general, perform reasoning. If the triple-store is a reasoning-triple-store, then the q functor can be used to also gather inferred triples. ↩
-
Remember that
select
is a macro and that using it in the REPL will produce interpreted code. This means that selects run in the REPL will be significantly slower than those that you write inside compiled functions. Both the HTTP and the Java interfaces to AllegroGraph ensure that any select calls get compiled before they run. Whether compiled queries execute significantly faster depends on whether the generated code performs a lot of backtracking or other repeated computation. ↩