The Semantic Layer, From ScratchChapter 3
Chapter 3

Names, values and files

How things get globally unique names, why every value carries a type, how triples are written to files, and where they're kept.

Carolyn Inworth has spent exactly $502,529.42 with us; Redshift says so, to the cent. Now write that number into a graph twice. Label it once as a decimal and once as a float, and read both back. The decimal returns $502,529.42. The float returns $502,529.40625. Same number, same database; the only difference is one word saying what type of value it is, and that one word puts one customer's total two cents off. This chapter is about the unglamorous things that decide whether a knowledge graph is right or almost right: how things are named, how values carry their types, how triples are written in files, and which folder they're kept in.

3.1Names that mean the same thing everywhere

In Aurora, 225 means Carolyn only because it sits in the cid column of c360.customer. In Redshift, product.id = 225 is a product. A key means something only together with its table.

A graph has no tables to supply that context, and triples from different sources mix freely (Figure 2.2). So every thing needs a name that means the same everywhere and can't collide. That's what IRIs are for:

TermWhat it isWhy it existsTiny example
IRI (Internationalized Resource Identifier)A globally unique name in URL/URN form. It needn't point anywhere; Stardog never fetches it.So a name means the same in any system.urn:stardog:demos:c360:customer:225
URNAn IRI scheme (urn:) for names that aren't web locations.Naming things without pretending they're web pages.Our data IRIs.
tag: IRIAn IRI scheme (RFC 4151) for minting unique names without registering anything.A cheap, collision-free vocabulary namespace.tag:stardog:api:ecomm:firstName
Namespace / local nameThe shared beginning of a family of IRIs / the part after it.Lets a prefix abbreviate the common part (Section 3.4).tag:stardog:api:ecomm: + firstName

One property of IRIs catches everyone: they're compared as exact strings. …customer:225, …Customer:225 and …customer:0225 are three different things. One stray character and you have a stranger.

A DATA IRI: NAMES ONE THING urn:stardog:demos:c360:customer:225 scheme project namespace kind of thing key from cid Built from the row's key by a template: …c360:customer:{cid} Same key, same name, every time. A VOCABULARY IRI: NAMES ONE PROPERTY tag:stardog:api:ecomm:firstName namespace, nicknamed ":" or "ecomm:" local name With a prefix declared, you write :firstName and Stardog expands it back.
Figure 3.1: Anatomy of our two kinds of IRI. Data IRIs are built from row keys; vocabulary IRIs share a namespace that a prefix abbreviates.

3.2Names built from keys: a preview of the IRI trick

Nobody types urn:stardog:demos:c360:customer:225 by hand. It's computed from the row's key by an IRI template: a fixed string with a slot for a column, like urn:stardog:demos:c360:customer:{cid}, so the same row gets the same name every time, from any system (Chapter 8 writes them). Every c360 thing follows one pattern, urn:stardog:demos:c360:<kind>:<key>:

KindTemplate (after urn:stardog:demos:c360:)Key column(s)
Customercustomer:{cid}Aurora customer.cid, and Redshift purchase.cid
Addresslocation:{id}Aurora address.id (and customer.location)
Credit card · rewards accountcards:{id} · rewards:{id}Aurora credit_card.id · rewards_account.id
Orderpurchase:us:pos{id}Redshift purchase.id
Product · category · unit priceproduct:{id} · category:{id} · unitprice:{id}Redshift product.id, category.id
Vendor · industryvendor:{vendor_name} · industry:{industry}Redshift vendor columns

The customer template appears in both databases. Here's the actual line from the Aurora mapping (Chapter 8) and the Redshift mapping (Chapter 9):

# Aurora mapping, c360.customer
BIND(TEMPLATE("urn:stardog:demos:c360:customer:{cid}") AS ?iri)

# Redshift mapping, c360.purchase
BIND(TEMPLATE("urn:stardog:demos:c360:customer:{cid}") AS ?cust_iri)
Aurora · c360.customer cid = 225 · Carolyn Inworth Redshift · c360.purchase cid = 225 · 22 × Flip Flops same template …customer:{cid} urn:stardog:demos: c360:customer:225 one name = one node the two databases meet here 225 225

Both produce the identical IRI. Same name, same node: this is where the two databases meet.

Figure 3.2: The IRI trick. Two rows in two engines pass their cid through the same template, so they produce the same name, and the graph joins them for free.

Both sides produce exactly the same string for Carolyn, and triples that use the same name are about the same node (Section 2.4). The blog calls this the IRI template; this book calls it the IRI trick, and Chapter 9 shows the machinery. For now, notice that it's nothing more than an agreement about spelling.

An IRI is a globally unique primary key, for things and for properties alike.

Example: customer:225 and product:225 never collide, while Aurora's and Redshift's cid 225 become the same node.

3.3Values carry their types

TermWhat it isWhy it existsTiny example
DatatypeAn IRI saying how to read a literal's characters. RDF borrows XML Schema's, prefixed xsd:.So 22 can be compared and summed as a number, not text.xsd:integer
Typed literalA value written as characters, ^^, and its datatype.The type travels with every single value."2011-01-21"^^xsd:date
Language tagA marker on a string saying which language it's in.Multilingual labels. Not used in c360."Milwaukee"@en

The types in c360, with SQL cousins and shorthand:

DatatypeFull formShorthandSQL cousinc360 use
xsd:string"Carolyn"quoted, no ^^varcharnames, email, state
xsd:integer"22"^^xsd:integer22integerorder quantity
xsd:decimal"439.78"^^xsd:decimal439.78numericorder price
xsd:float / xsd:double"24.0"^^xsd:float4.3978E2 (a double)real / double precisionproduct list price, in the kit's model
xsd:date"2011-01-21"^^xsd:datenonedatepurchase date

The type decides how values sort, compare and add up. As strings, "9" sorts after "10", so "top orders by quantity" with a string quantity is wrong in a way that looks almost right. FILTER(?price > 250), the heart of the "large order" rule (Chapter 10), only works if ?price is a number; YEAR(?date) only works on a real date. And then there's arithmetic.

Money is xsd:decimal, never xsd:float.

Example: 502,529.42 stored as a float comes back as 502,529.40625.

When the data lives in Aurora and Redshift, a value's type comes from the mapping. The ontology can say what type a property is meant to have, but that documents rather than converts (Chapter 6). So the working mappings cast explicitly: xsd:decimal for money, xsd:integer for quantities, xsd:date for dates. Without casts, values arrive with whatever type the database driver reports.

3.4Prefixes: nicknames for namespaces

Typing <tag:stardog:api:ecomm:firstName> every time, as you did in Chapter 2, gets old fast.

TermWhat it isWhy it existsTiny example
PrefixA declared nickname for a namespace.Readability; fewer typos in long IRIs.PREFIX : <tag:stardog:api:ecomm:>
Prefixed nameNickname + local name, expanded by plain text concatenation.Short to write, identical in meaning to the full IRI.:firstName = <tag:stardog:api:ecomm:firstName>

Ours is : (also written ecomm:) = tag:stardog:api:ecomm:. The standard ones: rdf: (core terms, chiefly rdf:type), rdfs: (labels, classes; Chapter 5), owl: (richer ontology terms; Chapter 6), xsd: (datatypes) and, in the kit, so: for https://schema.org/. Full namespaces are in the Turtle file in Section 3.7.

Because expansion is pure text, one IRI can have several spellings. The kit's rules file declares stardog: as tag:stardog:api: and writes stardog:ecomm:Customer, which expands to exactly :Customer. Stardog only compares the expanded form.

3.5Turtle: triples in a file

Triples need a file format, the way tables have CSV and SQL dumps. The one people read and write is Turtle (.ttl), a compact text format, because one triple per line with full IRIs is correct but exhausting. The kit's ontology files are Turtle, and so is what Stardog Designer exports. Watch three facts shrink.

Full form, one triple per line, each ending in a full stop. Valid Turtle already:

<urn:stardog:demos:c360:customer:225> <tag:stardog:api:ecomm:firstName> "Carolyn" .
<urn:stardog:demos:c360:customer:225> <tag:stardog:api:ecomm:lastName>  "Inworth" .
<urn:stardog:demos:c360:customer:225> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <tag:stardog:api:ecomm:Customer> .

With prefixes, a semicolon, and a. In a file, prefixes are declared with @prefix and a full stop. A semicolon means "same subject, next predicate". And a is short for rdf:type, the predicate that says what kind of thing something is:

@prefix : <tag:stardog:api:ecomm:> .

<urn:stardog:demos:c360:customer:225> a :Customer ;
    :firstName "Carolyn" ;
    :lastName  "Inworth" .

Turtle punctuation: ; means "same subject, next predicate", . means "done with this subject", , means "same subject and predicate, another object". Indentation means nothing.

Example: customer:1 a :Customer ; :name "Jolynn Bithany" . is two triples, written out in full as customer:1 a :Customer . and customer:1 :name "Jolynn Bithany" . In SQL terms, ; is the next column of the same row and . ends the row.

A comma means "same subject and predicate, another object". This line from the kit's schema.ttl is four triples:

:name so:domainIncludes :Category, :Customer, :Product, :Vendor .

That's enough to read the kit's ontology, which you'll load in Chapter 6. Its opening lines say "Industry is an OWL class labeled 'Industry'", and so on:

:Industry a owl:Class ;
    rdfs:label "Industry" .

:Customer a owl:Class ;
    rdfs:label "Customer" .

# starts a comment (like SQL's --), and whitespace doesn't matter. One trap: SPARQL declares prefixes as PREFIX, with no @ and no full stop, a frequent typo.

3.6Named graphs: folders inside the database

TermWhat it isWhy it existsTiny example
Named graphA set of triples labeled with its own IRI.To organize, secure, query and delete groups of triples as a unit.<urn:book:ch03>
Default graphThe one unnamed graph every database has.Where data goes when no graph is named.Empty in our design.
QuadA triple plus the graph it lives in.What a store with named graphs actually records.customer:225 firstName "Carolyn" in <urn:book:ch03>
database kit-c360 default graph no name · where unlabeled data lands · stays empty <urn:stardog:training- c360:1.0:model> the ontology · ch5–6 <urn:stardog:c360_extended _rules:1.0:model> the rules · ch10 <urn:book:ch02>, <…ch03> scratch · dropped after use stored inside Stardog (materialized) not stored: read live from SQL at query time (ch7–9) <virtual://aurora_c360_safe> <virtual://aurora_c360_pii> <virtual://redshift_c360>
Figure 3.3: Named graphs as folders. Solid folders hold triples stored in Stardog; dashed ones are virtual graphs whose triples are produced from SQL on demand. The default graph stays empty in our design.

The build uses these folders four ways. Organization: the ontology and the rules each get a graph, so each can be reloaded alone (Chapter 6). Cleanup: DROP GRAPH removes a whole folder. Provenance: each virtual graph has a name like <virtual://redshift_c360>, so a query can say which source it means. Security: access can be granted per graph (Chapter 12).

What does a query see? By default, a Stardog query that doesn't name a graph reads only the default graph. The database option query.all.graphs widens that to all local graphs, and special names such as <tag:stardog:api:context:all> mean "every local and virtual graph" (see Querying Stardog in the docs). This book's habit is simpler: always say which graph you mean, with GRAPH <…> { … }.

3.7Hands-on: a Turtle file in its own folder

You'll write a Turtle file about Carolyn, her address and one of her orders, load it into <urn:book:ch03>, inspect the types, and delete it. Load it from a file with Studio's Load Data dialog (Path A) or by pasting an INSERT DATA (Path B). Do one, not both.

Check yourself

Why can't RDF just use 225 as Carolyn's identifier, the way the customer table does?

A graph has no table to supply context, and 225 is also a product id in Redshift. Triples from many sources mix freely, so each name must be globally unique, as urn:stardog:demos:c360:customer:225 is.

What makes Carolyn's Aurora triples and her Redshift orders connect?

Both mappings build her IRI from cid with the identical template urn:stardog:demos:c360:customer:{cid}: same name, same node. Differ by one character and the join silently returns nothing.

What is the datatype of 439.78 unquoted in Turtle, and of "439.78" in quotes?

Unquoted, it's shorthand for xsd:decimal. Quoted with no ^^, it's an xsd:string and will sort and compare as text.

Where did the two cents go, and what brought them back?

The price was mapped as xsd:float, a 32-bit binary type that can't hold most decimal fractions exactly; summing many prices accumulated the error. Casting to xsd:decimal in the mapping made the total match SQL: $502,529.42.

Write in compact Turtle: customer 225 is a Customer, first name "Carolyn", last name "Inworth".

<urn:stardog:demos:c360:customer:225> a :Customer ; :firstName "Carolyn" ; :lastName "Inworth" . with @prefix : <tag:stardog:api:ecomm:> . declared. a means rdf:type; ; repeats the subject.

You loaded a file and GRAPH <urn:book:ch03> finds nothing. Likely cause?

It was loaded without a named graph, so its triples went into the default graph.

Chapter recap