Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Linux + Windows test matrix. The Linux leg exists deliberately: the LWS
# symlink-escape test (LWSStorageServletTest.resolveWithinRejectsSymlinkEscape)
# auto-skips on Windows without symlink privileges, so Windows-only development
# never exercises that protection.
name: CI

on:
push:
branches: [ master, next ]
pull_request:

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 25
cache: maven
- name: Build and test
run: mvn -B -Plib clean test
64 changes: 64 additions & 0 deletions RDF_1.1-compliance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# BeakGraph RDF 1.1 Compliance Assessment

*Assessed 2026-07-02 against [RDF 1.1 Concepts and Abstract Syntax](https://www.w3.org/TR/rdf11-concepts/) (W3C Recommendation, 25 February 2014).

## Verdict

Substantially compliant for the core data model as an RDF 1.1 dataset store queried through SPARQL. Three deliberate, documented deviations exist, of which one (numeric literal canonicalization) affects term identity.

## Compliant areas

### Term model and positions
- Subjects are IRI/blank node, predicates IRI, objects IRI/blank node/literal, graph names IRI/blank node — enforced with loud failures rather than silent acceptance of generalized RDF (`PositionalDictionaryWriterBuilder.ProcessQuad`, mirrored in `HugeBuildPipeline.countEntityKind`).
- RDF-star quoted triples are rejected with an exception. Correct behavior for an RDF **1.1** store; quoted triples are RDF 1.2 territory.

### Literals
- Every literal carries its datatype IRI, per RDF 1.1 (simple literals are `xsd:string`; identification inherited from Jena 5).
- `rdf:langString` is fully supported: the `langs`/`langTags` datasets store language tags separately and literals reconstruct term-exact (`MultiTypeDictionaryWriter` / `MultiTypeDictionaryReader`). Tag normalization policy is inherited from Jena's parsers and applied consistently on both write and query paths.
- Ill-typed literals (e.g. `"abc"^^xsd:int`) are valid RDF 1.1 terms and are preserved term-exactly through the strings path instead of being rejected or "repaired".
- Unicode: UTF-8 storage throughout; the front-coded dictionary never splits UTF-16 surrogate pairs (`FCDWriter.commonPrefixLength`).

### Blank nodes
- Blank node identifiers are not part of the RDF 1.1 abstract syntax; graphs are defined up to isomorphism. Rank-based storage (labels regenerated from dictionary ids on read) and the huge writer's keep-original-labels policy are both compliant under that rule.
- Blank node sharing across named graphs within one dataset is preserved, matching TriG/N-Quads dataset scoping.

### Datasets
- Default graph plus named graphs, with IRI and blank-node graph names supported. The default graph is held under ARQ's sentinel IRI internally — a representation detail invisible at the SPARQL level (`NodeComparator` even orders the two ARQ sentinels as distinct terms so data that uses them survives).
- Union-graph queries apply RDF-correct set semantics (explicit row dedup in `HDF5Reader.readUnion`).

### Syntax and query semantics
- Parsing/serialization and SPARQL 1.1 semantics are delegated to Jena 5.x (RIOT/ARQ); BeakGraph sits below as storage.
- The value-ordered dictionary preserves term identity: `NodeComparator` breaks value-equal ties on the exact RDF term (`"1"^^xsd:int` vs `"1"^^xsd:integer` keep distinct ids), and provides a self-consistent total order for timezone-sensitive temporal value spaces that agrees with XSD order wherever XSD order is determinate.

## Deviations

### 1. Numeric lexical canonicalization (affects term identity)
`canonicalizeNumericObject` rewrites `xsd:int` / `xsd:long` / `xsd:float` / `xsd:double` **objects** to canonical lexical form at ingest (`"01"^^xsd:int` → `"1"^^xsd:int`). Under RDF 1.1 these are distinct terms with equal values, so:

- the stored graph is value-preserving and D-entailment-equivalent to the source, but **not isomorphic** when the source contains non-canonical spellings;
- `sameTerm`-style lookups for the original spelling miss.

This is forced by the storage design: those four datatypes are stored by binary value and the reader regenerates the lexical form, so non-canonical spellings have nowhere to live. Strict term fidelity would require routing non-canonical numerics through the term-exact strings path (larger files, no numeric range pushdown for those literals). If archival round-tripping ever matters, this is the one behavior worth putting behind an off switch.

Note: `xsd:integer`, `xsd:decimal`, `xsd:dateTime`, booleans, and all other datatypes are stored term-exactly via the strings path. (The `T`-substring on `xsd:dateTime` in `ProcessQuad` feeds string-length *statistics* only; the stored literal is the full lexical form.)

### 2. Relative IRIs stored unresolved
RDF 1.1 abstract syntax requires absolute IRIs. BeakGraph deliberately stores document-relative references (`DataType.RELATIVE_IRI`) unresolved — parsed against a sentinel base, stripped back to relative form, and resolved against the serving URL at query time (`RelativeIRIResolver`). Until resolution, the in-file graph is not pure abstract syntax. This is an intentional extension serving the LWS/document use case.

### 3. Injected metadata graphs
The stored dataset is a superset of the source: VoID/SD metadata (`urn:x-beakgraph:void`) always, spatial index graphs (`urn:x-beakgraph:Spatial`, grid-tile URN graphs) when spatial indexing is enabled. Valid RDF, but `GRAPH ?g` enumerates graphs the source never contained — a faithfulness caveat rather than a spec violation. Relatedly, the `numQuads` attribute counts source quads only, excluding injected metadata.

## Summary table

| Aspect | Status |
|---|---|
| Term positions (S/P/O/G kinds) | Compliant, enforced |
| Literal datatypes, `xsd:string` identity | Compliant |
| `rdf:langString` / language tags | Compliant |
| Ill-typed literals | Compliant (preserved term-exact) |
| Blank node semantics | Compliant (isomorphism) |
| Datasets / named graphs | Compliant |
| Generalized RDF / RDF-star | Rejected loudly (correct for 1.1) |
| Numeric literal term identity | **Deviation** — canonicalized at ingest |
| Absolute-IRI requirement | **Deviation** — relative IRIs stored, resolved at serving time |
| Dataset faithfulness | **Caveat** — metadata graphs injected |
55 changes: 37 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
alt="BeakGraph"
title="BeakGraph">

Beakgraph is an [Apache Jena](https://jena.apache.org/) Graph Implmentation of [RDF HDT](https://www.rdfhdt.org/) technology pumped into a [HDF5](https://www.hdfgroup.org/solutions/hdf5/) file and extended to support a full RDF Dataset.
BeakGraph is an [Apache Jena](https://jena.apache.org/) Graph implementation of [RDF HDT](https://www.rdfhdt.org/) technology pumped into a [HDF5](https://www.hdfgroup.org/solutions/hdf5/) file and extended to support a full RDF Dataset.

<br clear="all">

Expand All @@ -30,42 +30,61 @@ Core Library Jar Library
mvn -Plib clean package
```

## Using BreakGraph in your code
## Using BeakGraph in your code

### Creating a BeakGraph from your data
```
HDF5Writer.Builder()
.setSource(file)
.setSpatial(true) # only needed if GeoSPARQL spatial data is present
.setDestination(dest)

The source syntax is detected from the file name (Turtle, TriG, N-Quads,
N-Triples; `.gz` compression is handled), so named graphs can be loaded from
quad-capable formats.

```java
BG.getBGWriterBuilder()
.setSource(new File("mydata.ttl"))
.setDestination(new File("mydata.ttl.h5"))
.setSpatial(true) // only needed if GeoSPARQL spatial data is present
.setFeatures(false) // optional: derive 2D shape features for geometries
.build()
.write();
```

### Using a BeakGraph with Apache Jena

```
File file = new File("mydata.h5");
try (BeakGraph bg = BG.getBeakGraph(file)) {
Dataset ds = bg.getDataset();
ds.getDefaultModel().write(System.out, "NTRIPLE");
}
```java
File file = new File("mydata.ttl.h5");
try (BeakGraph bg = BG.getBeakGraph(file)) {
Dataset ds = bg.getDataset();
ds.getDefaultModel().write(System.out, "NTRIPLE");
}
```

BeakGraph is a [Apache Jena](https://jena.apache.org/) Graph implementation backed by [HDF5](https://www.hdfgroup.org/solutions/hdf5/).
Beakgraph's HDF5 design is heavily inspired by [RDF HDT](https://www.rdfhdt.org/).

### Limitations

* extremely limited GeoSPARQL support (only sfIntersects)
* BeakGraph files are read-only; the writer builds them in one pass and holds
the working set in RAM (very large datasets may need a correspondingly large
heap).
* GeoSPARQL support covers `geof:sfIntersects` only. It is fully functional: a
recall-safe Hilbert cell-cover index produces candidate geometries and every
candidate is verified with real JTS geometry, so results are exact. Other
GeoSPARQL functions are not implemented.
* `.h5` files written before the spatial-index redesign carry the old
corner-based index entries, which the query side no longer reads - rebuild
them from source for spatial queries (their spatial answers were unsound
anyway; non-spatial queries are unaffected).
* Numeric literals typed `xsd:int`, `xsd:long`, `xsd:float` or `xsd:double` are
stored by value and canonicalized at ingest: `"01"^^xsd:int` is stored - and
matched - as `"1"^^xsd:int`.

### Author's notes
The first iteration of BeakGraph was backed by Apache Arrow instead of [HDF5](https://www.hdfgroup.org/solutions/hdf5/). An Apache Arrow version will return. Reasons for this are varied with some of these reasons being just experimentation.
The general idea of BeakGraph is a read-only, searchable, indexed set of binary [sussinct data structures](https://en.wikipedia.org/wiki/Succinct_data_structure) to represent an [RDF Dataset](https://www.w3.org/TR/rdf11-datasets/).
What these sussinct data structures are stored in, is somewhat immaterial, but the choice of container has it's pro and cons. HDF5 treats multi-dimensional arrays as first class citizens, and has a free viewer for
HDF5 files called [HDFView](https://www.hdfgroup.org/download-hdfview/). HDFView providing a nice way to debug the sussinct data structures during development. There are other perks to HDF5 which will become apparent in time.
The general idea of BeakGraph is a read-only, searchable, indexed set of binary [succinct data structures](https://en.wikipedia.org/wiki/Succinct_data_structure) to represent an [RDF Dataset](https://www.w3.org/TR/rdf11-datasets/).
What these succinct data structures are stored in, is somewhat immaterial, but the choice of container has its pros and cons. HDF5 treats multi-dimensional arrays as first class citizens, and has a free viewer for
HDF5 files called [HDFView](https://www.hdfgroup.org/download-hdfview/). HDFView provides a nice way to debug the succinct data structures during development. There are other perks to HDF5 which will become apparent in time.

Support for spatial indexing based on [GeoSPARQL](https://github.com/opengeospatial/ogc-geosparql) is being worked on.
Spatial indexing based on [GeoSPARQL](https://github.com/opengeospatial/ogc-geosparql) is supported for `geof:sfIntersects` (see Limitations above).

The full list of containers under consideration are:
* [HDF5](https://www.hdfgroup.org/solutions/hdf5/)
Expand Down
Loading
Loading