This document introduces AllegroGraph. It assumes that you are familiar with Lisp and 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, we suggest that you start with A Semantic Web Primer by Grigoris Antoniou and Frank van Harmelen (2001, Cambridge MA, MIT press; available, e.g. from www.amazon.com). It is a very gentle introduction to these new technologies. For a quick introduction, see these Wikipedia entries: OWL, RDF, and RDFS.
The AllegroGraph Tutorial
AllegroGraph only works with Allegro CL or higher. Please contact [email protected] to obtain a copy of Allegro CL 8.1 and AllegroGraph.
This tutorial shows AllegroGraph in action. You must install the AllegroGraph software before executing the examples shown below.
The code examples used in this tutorial were installed along with AllegroGraph, and reside in the agraph/tutorial-files/ subdirectory of the Allegro directory. On Windows, the Allegro directory is usually c:/Program Files/acl81.
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 with either alisp (ANSI Lisp, case-insensitive) or mlisp (Modern Lisp, case-sensitive). All of the examples in this document are from mlisp. To use AllegroGraph, start Allegro CL and evaluate the following forms:
(require :agraph)
(in-package :triple-store-user)
(setf *synchronize-automatically* t)
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 open 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 "temp/test", then AllegroGraph will create a new directory named temp in your current directory (use the top-level command :pwd if you want to see what this is). It will then make another directory named test as a sub-directory of temp. All of this triple-store's data will be placed in this new directory temp/test:
> (create-triple-store "temp/test" :if-exists :supersede)
We use temp/test throughout this document. You may want 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)
AllegroGraph already registered several namespaces for you that are common in the RDF(S) and OWL world. You can see them using:
> (display-namespaces)
Now we are ready to experiment with the reader macro.
A token beginning with a ! will expand to the full URI (in N-Triples notation). For example:
> !ex:Dog
!<http://www.franz.com/things#Dog>
> !ex:Cat
!<http://www.franz.com/things#Cat>
> !ex:dog
!<http://www.franz.com/things#dog>
> !ex:Dog
!<http://www.franz.com/things#Dog>
Evaluating these forms puts the following nodes into the string dictionary:
"<http://www.franz.com/things#Cat>"
"<http://www.franz.com/things#Dog>"
"<http://www.franz.com/things#dog>"
(Note that "Dog" and "dog" represent two different qualified names (QNames): RDF resources and literals are case sensitive).
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 :concise format will let (part->string) present a best-guess abbreviation of the URI. You can use the variable *default-print-triple-format* to control the default format used in printing.
> (part->string !ex:Cat :format :concise)
"ex:Cat"
Please realize that this function is only for development purposes. The :concise format of (print->string) will return just the string when called on a node number that refers to a literal.
> (part->string !"A string" :format :concise)
"\"A string\""
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.)
(By the way, you will recall that you set the variable *synchronize-automatically* to t above. This controls how often AllegroGraph updates the disk with any changes. If it is set to true, then AllegroGraph will update the disk with every change; otherwise, AllegroGraph won't update the disk unless you call sync-triple-store yourself (some other triple-store commands also synchronize). The disadvantage of having *synchronize-automatically* set to t is that some operations can be much slower since there will be excessive disk activity. For a tutorial like this, it's better to have it on but when running your own programs you should make sure that it is off or to wrap your functions in a let binding to set the value dynamically to nil.)
> (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 ids of the 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))
#(79 16 142 67 176 239 113 188 22 24 7 0 148 31 26
12 107 224 229 243 53 57 157 0 223 172 103 214 32
83 152 41 51 21 141 0 0 0 0 0 0 0 0 0 0 0 0 31
4 0 0 0 0 0 0 0)
A triple data structure is returned. This is a vector of 56 octets representing the four parts of the triples (!) and the 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)
#(79 16 142 67 176 239 113 188 22 24 7 0)
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.
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 variable *default-print-triple-format*.
> (print-triple tr :format :concise)
<triple 4: ex:Fido ex:has-owner ex:John default-graph>
> (dolist (e (get-triples-list)) (print-triple e :format :concise))
<triple 1: ex:Mammal rdf:type owl:Class default-graph>
<triple 2: ex:Dog rdfs:subClassOf ex:Mammal default-graph>
<triple 3: ex:Fido rdf:type ex:Dog default-graph>
<triple 4: ex:Fido ex:has-owner ex:John default-graph>
<triple 5: ex:Company rdf:type owl:Class default-graph>
<triple 6: ex:CommercialCompany rdfs:subClassOf ex:Company default-graph>
<triple 7: ex:Franz rdf:type owl:CommercialCompany default-graph>
<triple 8: ex:John ex:works-at ex:Franz default-graph>
<triple 9: ex:Franz ex:has-product ex:Agraph default-graph>
Note that the order of the triples returned from (get-triples-list) is unspecified so you may see a different result. Developers desiring to see the entire triple URI can set the keyword :format to :long or :ntriple.
> (print-triple tr :format :long)
<triple 4: http://www.franz.com/things#Fido
http://www.franz.com/things#has-owner
http://www.franz.com/things#John
default-graph>
> (print-triple tr :format :ntriple)
<http://www.franz.com/things#Fido>
<http://www.franz.com/things#has-owner>
<http://www.franz.com/things#John> .
When developing, and especially when using AllegroGraph interactively, it saves a lot or typing of print-triple to turn on another printing option. The function enable-print-decoded customizes the Lisp pretty printer to try to interpret octet arrays that appear to be either UPIs or triples. It takes a single boolean argument to turn this feature on or off:
> (enable-print-decoded t)
nil
> (get-triples-list :p !rdf:type :limit 5)
(<1: ex:Mammal rdf:type owl:Class>
<3: ex:Fido rdf:type ex:Dog>
<5: ex:Company rdf:type owl:Class>
<7: ex:Franz rdf:type owl:CommercialCompany>)
> (subject (first *))
{ex:Mammal}
> (type-of *)
(simple-array (unsigned-byte 8) (12))
The subject returned by the last form above is still a length 12 Lisp octet array, but the pretty printer has interpreted it for you. The format is intended to be a concise and convenient interpretation of a value and does not preserve encoding or language information. The curly brackets are a reminder you are seeing something different from what the Lisp object really is.
Printing nodes again
When developing, it is sometimes useful to print everything related to a particular node, for n levels deep. (pprint-subject part &key maximum-depth format) and (pprint-object part &key maximum-depth format) will do that for you.
> (pprint-subject !ex:Fido :format :concise)
ex:Fido ex:has-owner ex:John
ex:Fido rdf:type ex:Dog
> (pprint-subject !ex:Fido :maximum-depth 3 :format :concise)
ex:Fido ex:has-owner ex:John
ex:John ex:works-at ex:Franz
ex:Franz rdf:type owl:CommercialCompany
ex:Franz ex:has-product ex:Agraph
ex:Fido rdf:type ex:Dog
ex:Dog rdfs:subClassOf ex:Mammal
ex:Mammal rdf:type owl:Class
> (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 means 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)
<triple 4: ex:Fido ex:has-owner ex:John default-graph>
> (print-triples (get-triples-list :s !ex:Franz :p !rdf:type)
:format :concise)
<triple 7: ex:Franz rdf:type owl:CommercialCompany default-graph>
> (print-triples (get-triples-list :s !ex:John) :format :concise)
<triple 8: ex:John ex:works-at ex:Franz default-graph>
A cursor version
(get-triples-list) is built on the cursor based query function (get-triples) that you can use yourself. The first return value is the cursor that you can use. The second return value is the query plan which can be safely ignored for now.
> (get-triples :s !ex:Franz)
#<db.agraph::row-cursor
#<db.agraph::triple-record-file @ #x1001c670d2> 7 [1 - 9] @ #x100211a1e2>
#<db.agraph::query-plan index :spogi, 1 subplan @ #x100211a0c2>
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-by-number :s s :p p :o o :g g)
while (next-p cursor) collect (copy-triple (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!
Notes on add-triple
The function add-triple takes either strings or UPIs. If the argument is a UPI AllegroGraph assumes that it is a valid. It is slightly more complicated to understand what happens when you use strings for 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
!<http://www.franz.com/things#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 arg) and (literal arg). :
> (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)
<triple 10: Peter rdf:comment Peter default-graph >
nil
> (close-triple-store)
nil
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/" :if-exists :supersede)
Load in the file,
> (load-ntriples "sys:agraph;tutorial-files;wilburwine.ntriples")
2012
#(0 0 0 0 0 0 0 0 0 0 ...)
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 "sys:agraph;tutorial-files;wilburwine.rdf"). You can also see the number of triples 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)
(#(22 248 167 148 97 40 45 66 55 197 ...)
#(22 248 167 148 97 40 45 66 55 197 ...)
#(58 170 243 65 207 10 218 228 241 174 ...)
#(22 248 167 148 97 40 45 66 55 197 ...)
#(22 248 167 148 97 40 45 66 55 197 ...)
#(22 248 167 148 97 40 45 66 55 197 ...)
#(22 248 167 148 97 40 45 66 55 197 ...)
#(224 255 49 73 209 53 100 146 132 55 ...)
#(224 255 49 73 209 53 100 146 132 55 ...)
#(1 0 0 0 36 244 74 0 0 0 ...) ...)
#<db.agraph::row-cursor
#<db.agraph::triple-record-file @ #x100257a2c2> 51 [1 - 2012] @ #x1002ae0a42>
> (length *)
50
And remember you can always ask the printer to interpret triples for you:
> (enable-print-decoded t)
t
> (get-triples-list)
(<1: wine rdf:type owl:Ontology>
<2: wine rdfs:comment "An example OWL ontology">
<3: wine rdf:type owl:Ontology>
...)
#<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.
> (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 some more namespaces,
> (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))
43
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. (btw: the function takes either strings or UPIs...) :
> (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).
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))a
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 pause/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}
No.
Well, that was painful. Six triples to say something about one other triple. In AllegroGraph we therefore provide first-class triples.
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.
> (setq tr (first (get-triples-list
: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>
We could take a shortcut and say this:
> (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, 2021 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 different approach.
> (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)))
<triple 2014: guide:wine ex:better-than ex:beer default-graph>
?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 "temp/test"
:if-exists :supersede)
#<db.agraph::triple-db temp/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 making a different 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.
> (?- (q ?x ?y ?z))
?x = {wine}
?y = {rdf:type}
?z = {owl:Ontology}
?x = {wine}
?y = {rdfs:comment}
?z = {"An example OWL ontology"}.
;; type #\. (period)
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 subClassOf relations in the triple-store.
> (?- (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.
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. 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). 1
> (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.
Footnotes
-
Remember that
selectis 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. ↩