Taking memory out whole
tm export has always written the live statements as N-Triples or N-Quads. Those formats lose what makes tiramemsu what it is. They drop the statement ids, so an annotation on a fact can no longer point at that fact. They drop retracted statements, so the history is gone. And they drop both clocks. The new format keeps all of it:
tm export memory.db --format sg-nt12 # assert what is live now
tm export memory.db --format sg-nt12 --as-of 3 # ... what was live after transaction 3
tm export memory.db --format sg-nt12 --history # ... every statement
tm export memory.db --format sg-nt12 --valid-at 1800000000000 # ... only what is valid at that instant
"sg" stands for statement graph, in the paper's sense: Gelling, Fletcher and Schmidt's model, where each statement is a vertex with subject, predicate and object edges. This is not tiramemsu's named graph named by a statement, which shares the name. "nt12" stands for RDF 1.2 N-Triples, the format of the output.
One statement, four kinds of line
Take a route and a distance about it. The distance is entered wrong, as 665, and corrected to 655 by a supersede in transaction 4, together with a note on where the right number came from. A summer route has a valid interval:
tx 1 e1 = (:FRA, :route, :LHR)
tx 2 e2 = (e1, :distance, 665)
tx 3 e3 = (:LHR, :route, :JFK) valid [2026-03-30, 2026-10-24)
tx 4 e4 = (e1, :distance, 655) supersedes e2, which is retracted
e6 = (e4, :source, :airlineDB)
Every statement becomes a reifier: an IRI that stands for that occurrence and names, through rdf:reifies, the triple it is about. Its clocks come with it. Here is e2, the wrong distance, in the export (prefixes shortened):
stmt:2 rdf:reifies <<( stmt:1 :distance 665 )>> .
stmt:2 tm:txAdded 2 .
stmt:2 tm:txRetracted 4 .
A reference to another statement is written as that statement's IRI, here stmt:1. It is the IRI SPARQL already returns for a statement id. Every clock is an xsd:integer: transaction numbers, and valid time in epoch milliseconds.
stmt:3 rdf:reifies <<( :LHR :route :JFK )>> .
stmt:3 tm:txAdded 3 .
stmt:3 tm:validFromMs 1774828800000 .
stmt:3 tm:validToMs 1792800000000 .
The view decides what is asserted
Reifiers say that a statement existed. Whether its triple is asserted depends on the view, exactly as for a query. Every reifier appears in all four exports. Only the asserted lines differ:
| View | Asserted triples |
|---|---|
now | |
--as-of 3 | |
--valid-at 1800000000000 | |
--history | |
As of transaction 3, the wrong distance was what we believed, so it is asserted, and the correction and its source do not exist yet. On 15 January 2027 the summer route is not valid, so it is not asserted. The history asserts both distances, and their reifiers show which one was retracted and when.
What is left out
- Bookkeeping.
sys:statements belong to the engine, as in the plain export. The supersede wrote one,(e4, sys:supersedes, e2), as eid 5, which is why the example jumps fromstmt:4tostmt:6. - Statements that were never visible. A statement added and retracted in the same transaction has no view in which it was ever true, and it is not exported.
Two things are refused rather than written. One is a plain IRI that starts with urn:tiramemsu:stmt:, which would read back as a statement id. The other is rdf:reifies used as an ordinary predicate.
Why tm:validFromMs
In SPARQL and Cypher, tm:validFrom and tm:validTo are virtual predicates whose values are xsd:dateTime. The export writes milliseconds, so it uses names of its own: tm:validFromMs and tm:validToMs. No IRI means two things. Why milliseconds at all is the subject of the companion article: they are the form the decoder is proven on.
Checking a file
The format is defined by LeanRDF, a Lean 4 library of RDF semantics. Its decoder is proven exact. A file decodes only if it is exactly the export of a well-formed set of statements, and decoding an export gives back the statements:
tm export memory.db --format sg-nt12 --history > memory.nt
cd rdf-lean/statements
lake exe leanrdf-sg decode ../memory.nt --history # exit 0 and the records as JSON lines, or exit 2 and what is wrong
tiramemsu writes LeanRDF's canonical form, with its triple order and string escapes. Decoding a file with LeanRDF and exporting the result again gives back the same bytes. LeanRDF's test suite checks that on every build, for every file tiramemsu's export tests record.
Not supported
- Reading it back. The format is export-only:
tm importrefuses triple terms, and the clock triples would not become intervals again. - Named graphs. Graph memberships are not part of the format. Use N-Quads for those.
The practical guide is docs/statement-graph-export.md. The paper behind the format is When Is a Temporal Statement Store a Statement Graph?