Skip to content

Term types — typed literals, language tags, blank nodes, RDF lists ​

Ingest keeps every kind of RDF term: IRIs, plain literals, typed literals, language-tagged literals, blank nodes and RDF collections.

What it does ​

TermIn TurtleHow SPARQL sees it
IRI<http://example.org/alice>isIRI(?x) is true
Plain literal"hello"datatype xsd:string (RDF 1.1)
Typed literal"42"^^xsd:integerDATATYPE(?x) is xsd:integer; compares as a number
Language-tagged literal"colour"@en-GBLANG(?x) is "en-GB"
Blank node_:b1 or [ … ]isBlank(?x) is true; shows as a generated label
RDF collection( a b c )expands to an rdf:first / rdf:rest / rdf:nil chain

Literals are kept distinct on their full identity: value, datatype and language together. "Berlin"@en, "Berlin"@de and "Berlin" are three different terms, as are "1"^^xsd:integer and "1".

Two presentation details:

  • pgrdf.sparql() returns every value as a JSON string, numbers included ({"a": "30"}). Use DATATYPE() / LANG() in the query, or construct(), which returns each term with its type, datatype and language.
  • RDF-star quoted triples are not supported and refuse at load time.

Why you'd use it ​

  • Data scientists — numeric and language filters work on real data because types survive ingest instead of being flattened to strings.
  • Ontologists — OWL, SHACL, PROV-O and similar vocabularies load with their datatype assertions intact.
  • Project managers — what is stored matches the W3C RDF model, so there are no lossy surprises downstream.

Example ​

sql
SELECT pgrdf.add_graph('http://example.org/terms');

SELECT pgrdf.parse_turtle('
@prefix ex:  <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ex:alice ex:name  "Alice"@en ;
         ex:age   "30"^^xsd:integer ;
         ex:notes [ ex:source "manual" ] ;
         ex:tags  ( "engineer" "rust" "rdf" ) .
', pgrdf.graph_id('http://example.org/terms'));
-- → 11   (4 statements about alice, 1 for the blank node, 6 for the 3-item list)

A numeric filter over the typed literal:

sql
SELECT * FROM pgrdf.sparql('
  PREFIX ex: <http://example.org/>
  SELECT ?s ?a WHERE { ?s ex:age ?a FILTER(?a >= 18) }');
--  {"a": "30", "s": "http://example.org/alice"}

Language and datatype:

sql
SELECT * FROM pgrdf.sparql('
  PREFIX ex: <http://example.org/>
  SELECT ?n (LANG(?n) AS ?l) (DATATYPE(?a) AS ?dt)
   WHERE { ?s ex:name ?n ; ex:age ?a FILTER(LANG(?n) = "en") }');
--  {"l": "en", "n": "Alice", "dt": "http://www.w3.org/2001/XMLSchema#integer"}

Following a blank node, using a variable for it:

sql
SELECT * FROM pgrdf.sparql('
  PREFIX ex: <http://example.org/>
  SELECT ?source WHERE { ex:alice ex:notes ?note . ?note ex:source ?source }');
--  {"source": "manual"}

The items of an RDF list:

sql
SELECT * FROM pgrdf.sparql('
  PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  SELECT ?item WHERE { GRAPH <http://example.org/terms> { ?node rdf:first ?item } }');
--  {"item": "engineer"}
--  {"item": "rust"}
--  {"item": "rdf"}

Blank nodes written inside a query pattern ([ ex:source ?s ]) and sequence paths (rdf:rest*/rdf:first) are not supported. Use a variable, as above. See property paths.

Distinct literals that share a value:

sql
SELECT pgrdf.add_graph('http://example.org/berlin');
SELECT pgrdf.parse_turtle('
@prefix ex:  <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:c ex:label "Berlin"@en, "Berlin"@de, "Berlin" .
ex:c ex:n     "1"^^xsd:integer, "1" .
', pgrdf.graph_id('http://example.org/berlin'));
-- → 5

SELECT * FROM pgrdf.sparql('
  SELECT ?o (LANG(?o) AS ?l) (DATATYPE(?o) AS ?d)
   WHERE { GRAPH <http://example.org/berlin> { ?s ?p ?o } }');
--  {"d": null, "l": "en", "o": "Berlin"}
--  {"d": null, "l": "de", "o": "Berlin"}
--  {"d": "http://www.w3.org/2001/XMLSchema#string", "l": "", "o": "Berlin"}
--  {"d": "http://www.w3.org/2001/XMLSchema#integer", "l": "", "o": "1"}
--  {"d": "http://www.w3.org/2001/XMLSchema#string", "l": "", "o": "1"}

See also ​

pgRDF is released under the MIT license. Documentation built with VitePress, served via GitHub Pages.