AllegroGraph Lisp Quick Start
This document demonstrates how to use the AllegroGraph Lisp Client with the AllegroGraph server.
The Quick Start guide presents a series of hands-on experiments for the reader to perform interactively in the Allegro Common Lisp (ACL) Interactive Development Environment (IDE). The exercises demonstrate how to use the ACL Client to create AllegroGraph repositories; how to move data into (and out of) the triple store; how to organize data into subgraphs; and how to perform various types of queries on the stored triples.
Client general information
You can communicate with the AllegroGraph server in various ways using several interfaces. Programs that communicate with the server are called clients. A typical client is AGWebView in a web browser, described in the WebView document. The Lisp client communicates with the server from a running Allegro CL image.
There are two types of Lisp client: the direct client and the remote client. The direct client must be run on the same machine and by the same user as the server. The remote client can run on any machine that can commuicate with the server machine. The direct client is basically a port to the internals of the server. Someone using the direct client has full control over the database, and therefore, for production system, access to the direct Lisp client should be limited to fully trusted users.
The remote Lisp client communicates with the server, as do all other clients, through the REST interface. Much functionality available to a direct Lisp client is not available to a remote Lisp client.
To use a Lisp client, you must have the appropriate compiled Lisp file, called a "fasl" (pronounced faz-ull) file.
On Linux 64-bit, SMP or non-SMP, (the platforms on which the server can run), a single fasl file is used for both the direct and the remote clients. If you are running on that platform, you must run the remote client if the machine is not the one running the server or if you are running as a different user than the user who started the server. If you are on the same machine as the server and are the user who started the server, you can run a direct client or a remote client (depending on arguments to functions like open-triple-store).
On all other platforms, the fasl file is for the remote Lisp client. All client fasl files are named agraph.fasl. Users have to keep the files for the various platforms separate and load the appropriate one.
Both AllegroGraph and Allegro CL are products of Franz Inc. However, each product has it own release numbers and release schedules which are not coordinated. Not all version of Allegro CL will work with all versions of AllegroGraph and vice versa.
Currently supported versions of Allegro CL are 8.2, 9.0, and 10.0. We next list what versions of Lisp work with the current version of AllegroGraph (version 6.4.1).
Version of Allegro CL needed for the direct Lisp client
AllegroGraph 6.4.1 will only work with direct Lisp clients running on Allegro CL 10.0 (either SMP or non-SMP) for Linux 64-bit. Unless noted, it will also work with later releases. You cannot run a direct Lisp client for AllegroGraph 6.4.1 with any earlier version of Allegro CL.
Version of Allegro CL needed for the remote Lisp client
Because the remote Lisp client communicates with AllegroGraph via HTTP using the REST interface, any remote Lisp client will "work" with an AllegroGraph 6.4.1 server. We put "work" in quotation marks because using a remote Lisp client based on an earlier version of AllegroGraph may:
not contain functionality documented in the AllegroGraph documentation. (For example, copy-triples does not work in pre-6.4.1 remote Lisp clients.)
contain functionality no longer supported.
Therefore, we recommend that users use the AllegroGraph 6.4.1 remote client. That remote client only works with Allegro CL 10.0 (on any platform). Unless noted, it will also work with later releases. We assume throughout this documentation that users are using the AllegroGraph 6.4.1 remote client.
Before You Begin...
This Quick Start guide shows the AllegroGraph Lisp Client in action. The AllegroGraph 6.4.1 Server runs natively on Linux x86 64-bit. The clients run on various platforms, listed below. If you want to work along, you will need to download various software packages.
Server Download
Navigate to the AllegroGraph download page and obtain this package:
- AllegroGraph Server 6.4.1 (or later), in the 64-bit Linux edition. You may install either agraph-6.4.1-linuxamd64.64.tar.gz or agraph-6.4.1-1.x86_64.rpm . This version of AllegroGraph server lets you load a maximum of 5 million triples, and has no expiration date.
Allegro CL Download
The current version of Allegro CL that works as the Lisp client for AllegroGraph 6.4.1 is 10.0. Later versions of Allegro CL may be released before this document is updated. Unless noted, these can also be used as clients for AllegroGraph 6.4.1. Just be sure that the version of Allegro CL that you download is the same as the AllegroGraph client that you download.
If you already have Allegro CL 10.0 installed on one of the platforms which support AllegroGraph clients (listed below), you can use the version you have installed. If you are not already a customer, navigate to the Allegro CL 10.0 download page and obtain the desired Allegro CL version (the same version(s) as you will download AllegroGraph client(s) listed below). Follow the instructions on that page for downloading. The bunzip2 utility, available on the download page, may be necessary to unpack the Allegro CL file. If bunzip2 is not already installed on your system you should download it.
Client Download
The current version of Allegro CL that works as the Lisp client for AllegroGraph 6.4.1 is 10.0. Later versions of Allegro CL may be released before this document is updated. Unless noted, these can also be used as clients for AllegroGraph 6.4.1. Just be sure that the version of Allegro CL that you download is the same as the AllegroGraph client that you download.
On the AllegroGraph Client Download page (accessible from the AllegroGraph download page page), the various AllegroGraph clients are listed with download links. For Lisp, look at the Allegro CL SMP and Allegro CL (non-SMP) sections. Then download the AllegroGraph client version for the platform(s) on which you wish to run the client. AllegroGraph 6.4.1 works with Allegro CL 10.0 and Allegro CL 10.0 SMP. You must choose the platform and the desired Allegro CL verion (SMP or non-SMP) when deciding which client files to download.
Installation
There are three installation tasks that must be completed before running the AllegroGraph Lisp Client:
The AllegroGraph Server: Installation of AllegroGraph 6.4.1 server on the Linux 64 platform is very straightforward. See the Server Installation instruction page for help installing this product.
Allegro CL: Installation of Allegro CL 10.0 on the desired platform platform is described on the Allegro CL installation
The AllegroGraph Client: To install the AllegroGraph Lisp Client, simply unpack the downloaded gzipped client tar file into a directory of your choice. On Unix machines, you might use the tar program directly from a terminal window:
$ cd /home/fred/Downloads
$ tar -zxf [downloaded client file for your platform].tar.gz
On Windows, you use a Windows unzip program.
Initialization
The Allegro Common Lisp requires a brief initialization process in order to download and install updates from Franz.com. If this has already been done (as is typical when installing Allegro CL following the installation instructions), you may proceed to the exercises. If not, please refer to Allegro Common Lisp Initialization and then return to this document when you are finished.
Loading the Lisp Client
From a terminal window, logged in as a non-adminstrative user, navigate to the Allegro CL installation directory and run Lisp:
# cd /usr/local/acl100/
# ./mlisp
Note that Allegro CL supports several types of Lisp executables, but not all of them are compatible with the AllegroGraph client. These exercises were composed using modern, case-sensitive mlisp. The case-insensitive executables (such as alisp) are not compatible with AllegroGraph and must not be used as an AllegroGraph client.
In the Lisp window, you may open the Interactive Development Environment (IDE) by issuing these two commands:
cl-user(1): (require :ide)
cl-user(2): (ide:start-ide)
Note that it may take a few seconds for the IDE window to open.
To load the AllegroGraph Lisp Client into Allegro CL, enter this form in the IDE, using the directory where you unpacked agraph-6.4.1-client-lisp.tar.gz.
:cd /home/fred/agraph-6.4.1-\[platform\]-client-lisp
(direct-client for Linux 64-bit.) Then, load agraph.fasl:
:ld agraph.fasl
Next, set up some basic niceties for interacting with AllegroGraph: the !-reader for reading URIs and literals in Lisp expressions by prefixing them with a "!" character, and a package in which AllegroGraph symbols are available.
;; Switch to the AllegrGraph "user" package
(in-package :db.agraph.user)
;; turn on the convenience !-reader
(enable-!-reader)
;; tell AllegroGraph to print triples more readably
(enable-print-decoded t)
Direct and Remote Triple Stores
The first thing we need to do is to create a triple store. Most of the functions described on this page will not work without an open triple store. When you are experimenting always have a triple store open.
The AllegroGraph Lisp Client can open a triple store in two different ways, depending on your needs.
Direct Connection: If the AllegroGraph server and Allegro Common Lisp are running on the same computer, the Lisp Client can make a direct connection to the AllegroGraph server. The direct connection bypasses AllegroGraph's security model and gives the Lisp user unrestricted access to all local repositories.
Remote Connection: The AllegroGraph Lisp Client can also access repositories on a remote server by providing a user name and password, as is done with AllegroGraph's other client packages. In this mode, the usual access-control safeguards are in place.
The following two sections demonstrate how to open both a local and a remote triple store.
Making a Direct Connection to a Triple Store
This section demonstrates how easy it is to open a direct connection to a triple store when AllegroGraph server and Allegro Common Lisp are running on the same computer.
The function create-triple-store creates a new triple store and opens it. If you use the triple-store name "ag-test":
> (create-triple-store "ag-test")
#<db.agraph::triple-db ag-test, 0, open @ #x10039e58e2>
Note that create-triple-store has several optional keyword arguments that default to a direct connection to a local AllegroGraph server. This assumes that the AllegroGraph server was installed with the usual default settings, such as port number 10035. If the server uses a different port number, you will need to state it explicitly using create-triple-store's :port keyword. You should also change the value of *default-ag-http-port*, which would subsequently provide the correct default port number to other functions:
> (setf *default-ag-http-port* 10035)
It may take several seconds for AllegroGraph to create (or to re-open) a triple store because it is starting multiple maintenance processes and readying on-disk data structures.
We will use ag-test
throughout this document but you can use any name you'd like. When you're done with a triple store, you can close it with close-triple-store
> (close-triple-store)
nil
and re-open it with open-triple-store:
> (open-triple-store "ag-test")
#<db.agraph::triple-db ag-test, 0, open @ #x1003cc39e2>
Connecting to a Remote Triple Store
To open a triple store on a remote server, we again turn to create-triple-store, but using a more explicit set of arguments. You need to know the server IP address or hostname. The :user and :password must already be set up (you created at least a superuser name and password when you installed the server -- here we use the standard "test" and "xyzzy" simply to show the parameter format). If you specify a catalog, it too must exist.
(create-triple-store "ag-test"
:triple-store-class 'remote-triple-store
:server "192.168.111.128"
; :catalog "my-catalog" ;; uncomment and use an existing catalog name
;; if desired
:port 10035 :user "test" :password "xyzzy")
:catalog will default to AllegroGraph's root catalog. Any named catalog (such as shown in the commented out line) must be predefined in the AllegroGraph server's agraph.cfg file. The :server must identify the machine where the AllegroGraph server is running.
To close the remote store, simply use close-triple-store as shown in the previous section.
Namespaces and the reader macro for resources
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 typing the namespace part of a URI over and over again, we use the exclamation mark to introduce a shorthand. To see ! in action, let's register a namespace:
> (register-namespace "ex" "http://franz.com/things#"
:errorp nil)
"http://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://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://franz.com/things#Dog>
!<http://franz.com/things#Dog>
> (part= !ex:Dog !<http://franz.com/things#Dog>)
t
;; the literal string "Dog"
> !"Dog"
Evaluating these forms puts the following nodes into the string dictionary:
"http://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://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:AllegroGraph)
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. 1 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 2 . 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://franz.com/simple#Peter")
!<http://franz.com/simple#Peter>
> (literal "Peter")
!"Peter"
> (let ((str1 "http://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://franz.com/simple#Peter rdf:comment Peter>
#(200 74 120 93 224 212 32 42 151 60 ...)
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.
> (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://franz.com/things#Fido> <http://franz.com/things#has-owner> <http://franz.com/things#John> .
<Fido has-owner John>
> (print-triple tr :format :ntriple)
<http://franz.com/things#Fido> <http://franz.com/things#has-owner> <http://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 ...))
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.
> (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::storage-layer-cursor <{Franz}---------> @ #x10042410d2>
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 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 many formats for reading and serializing data. These include N-Triples, Turtle, TriX, and RDF/XML. Here, we load the wine ontology in N-Triples format (In the following example, you will need to provide an appropriate path to the N-Ttriples file you want to load.)
> (load-ntriples "/home/fred/Download/wilburwine.ntriples")
2012
{G}
This reads the file, creates triples, and then returns the total number of triples created. The second return value is the graph into which the triples were loaded. Since we didn't specify, AllegroGraph has put the triples into the default graph
which it represents using ` `. Note, instead of load-ntriples you also could have used load-rdf/xml on the file wilburwine.rdf. You can see the total number of triples in the store using the triple-count function.
> (triple-count)
2012
> (get-triples-list :limit 50)
(<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>)
#<db.agraph::storage-layer-cursor <------------> @ #x10043b60e2>
> (length *)
50
Only 50 triples appear in the result list because that is the limit we specified in the call to get-triples-list.
Before we make any other changes, we'll commit what we've done so far with commit-triple-store:
> (commit-triple-store)
t
Now lets find information on 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
Now let us register another namespace,
> (register-namespace "ex" "http://franz.com/things#"
:errorp nil)
"http://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). First, commit:
> (commit-triple-store)
t
and then delete:
> (delete-triples :s !guide:Wine :o !ex:beer)
1
It returns the number of triples deleted. We can use rollback-triple-store to get the deleted-triple-back:
> (triple-count)
2012
> (rollback-triple-store)
t
> (triple-count)
2013
Using Prolog
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:
> (create-triple-store "ag-test")
#<db.agraph::triple-db ag-test, 0, open @ #x1003c16792>
> (load-ntriples "sys:agraph;tutorial-files;wilburwine.ntriples")
2012
{default-graph}
> (get-triples-list :p !rdfs:subClassOf)
(<Wine subClassOf PotableLiquid>
<Wine subClassOf _:anon1> <Wine subClassOf _:anon2>
<Wine subClassOf _:anon3> <Wine subClassOf _:anon4>
<Wine subClassOf _:anon5> ...)
> (register-namespace "ex" "http://franz.com/things#"
:errorp nil)
"http://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
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.
3
> (?- (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->number ?id))
:format :concise)))
<2018: guide:wine ex:better-than ex:beer>
?st = {_:anon460}
?id = {2018}.
No.
When you get this tutorial you also get the file "kennedy-family.cl". When you complete the current tutorial, you can evaluate the forms in "kennedy-family.cl" 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). 4
> (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")
...)
SPARQL
Running a SPARQL query is as simple as calling the function sparql:run-sparql. We'll start by emptying our triple store so our query will return no results. We use the :table
results-format to get a human-readable display of the output. You’ll see that Lisp strings (the query) can span multiple lines. Use the single-quote character for strings inside SPARQL queries to avoid the need for escaping.
> (delete-triples)
2013
> (sparql:run-sparql "
SELECT DISTINCT ?s {
?s ?p ?o
}
LIMIT 10"
:results-format :table)
---------------------------------------------------------------------------
| s |
===========================================================================
---------------------------------------------------------------------------
t
:select
(?s)
For the purposes of this example, let’s load some triples from the web and try another query. This time, we'll get the results back in JSON format.
> (load-ntriples
"http://www.w3.org/2000/10/rdf-tests/rdfcore/ntriples/test.nt")
30
{G}
> (sparql:run-sparql "
SELECT DISTINCT ?o {
?s <http://example.org/property> ?o .
FILTER (isIRI(?o))
}"
:results-format :sparql-json)
{
"head" : {
"vars" : ["o"]
},
"results" : {
"bindings" : [
{
"o":{"type":"uri","value":"http://example.org/resource2"}
}
]
}
}
t
:select
(?o)
You can see four values printed there. The results of the query are printed to the REPL in JavaScript Object Notation (JSON). Additionally, three values are returned: t (true, because the results have been printed), :select (the SPARQL verb used in this query), and (?o), the list of variables selected by the query. If you need to programmatically process the output of a query, these values will be useful.
Ordinarily, you’ll use a printed representation to see the results of your query (e.g., :sparql-json, :sparql-xml, :table), and a programmatic format (such as :arrays) to use the results in your own Lisp code.
You should now have a pretty good idea how to open a store and run a query against it from Lisp.
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
- Prolog and select
- SPARQL
To continue, see the other tutorials (linked in the navigation bar at the top of this page) and the AllegroGraph Learning Center.
Footnotes
-
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. ↩