Guide

Reading, writing and comparing RDF

Every example on this page is taken from examples/src/examples.adb, which is compiled and run by CI. An example that nothing builds stops being true without anyone noticing.

Install

[[depends-on]]
flyology_rdf = "*"

The Notation3 and SPARQL crates are separate dependencies, flyology_n3 and flyology_sparql. The library has no runtime dependency and no tasking.

Reading a document

A sink receives one event per statement as the parser reads it. Nothing is retained that the sink does not retain, which is what lets a document larger than memory be read.

type Collector is limited new Turtle_Parsers.Event_Sink with record
   Data : Datasets.Dataset := Datasets.Empty;
end record;

overriding procedure On_Quad
  (Target : in out Collector;
   Value  : Quads.Quad;
   Span   : Turtle_Parsers.Source_Span)
is
begin
   Datasets.Insert (Target.Data, Value);
end On_Quad;

Four syntaxes share the parser, chosen at Create: Turtle_Syntax, TriG_Syntax, NTriples_Syntax and NQuads_Syntax.

Input that arrives in pieces

The split may fall anywhere, including inside an escape or a literal, and what comes out does not depend on where it fell. Every document in the test suite is parsed twice, whole and one byte at a time, and the two must agree.

for Index in Document'Range loop
   Turtle_Parsers.Feed (Parser, Document (Index .. Index), Sink);
end loop;

Notation3 and SPARQL take chunks too. They emit nothing as they read, because a formula and a query are terms and a term means nothing until it is closed; what chunking buys there is the scanner.

Building statements

A predicate is typed as an IRI rather than a term, so a literal in predicate position is a compile error rather than a runtime check.

Datasets.Insert
  (Data,
   Quads.Create
     (Graph     => Quads.Default_Graph,
      Subject   => Terms.IRI_Term (I ("http://example.org/ada")),
      Predicate => I ("http://example.org/wrote"),
      Object    => Terms.Language_Literal ("notes", "en")));

Writing

Bind (Prefixes, "", "http://example.org/");
Put (Turtle_Writers.To_Turtle (Data, Prefixes));
@prefix : <http://example.org/> .

:ada :born "1815-12-10" ;
    :wrote :notes .

:notes :about "flight"@en .

Deciding whether two graphs say the same thing

Two documents that differ only in their blank node labels denote the same thing, and comparing their text will not tell you so. Canonicalization decides it: every blank node is labelled from its position in the graph rather than from what it was called.

Is_Isomorphic (Left, Right)   --  TRUE
Put (To_Canonical_NQuads (Left));
_:c14n0 <http://example.org/q> "x" .
_:c14n1 <http://example.org/p> _:c14n0 .

The algorithm has a documented adversarial case, so it takes a work bound and reports reaching it as a result rather than running until something else gives out. RDFC-1.0 admits SHA-256 and SHA-384; the digest is part of the request, because the identifiers a processor issues differ between them.

Notation3

N3 says things RDF cannot. A formula is a term, so a rule is a statement about two graphs.

Parsed : constant Model.Term :=
  N3_Parsers.Parse ("{ ?who :wrote ?what } => { ?what :by ?who } .", Base);

SPARQL

Queries are read and written back as documents. There is no evaluation: a query here is something to check, format or inspect.

Parsed : constant Syntax.Query := SPARQL_Parsers.Parse (Query);
Put (SPARQL_Writers.To_SPARQL (Parsed));

When a document is malformed

Failures are typed. A diagnostic carries a code, the grammar production that failed, and a full source span with byte, line and column at both ends — not a message string.

Status := Turtle_Parsers.Finish (Parser, Sink);
--  Parse_Failed, with the diagnostic already delivered to the sink