Skip to content

rocket_launchCONSTRUCT — graph-shaped result rows

The canonical SPARQL form for snapshotting a subgraph, materialising a view, or feeding a graph-rewrite pipeline. Currently shipping on main — Phase D, pre-release.

In flight

This surface is landing on the pgRDF main branch right now (Phase D countdown slices 54-59 — pgrdf.construct(q) foundation, variable substitution, blank-node templates, multi-triple templates, GRAPH-scoped WHERE, WHERE-shorthand). It will tag in the next release (post-v0.4.3). The shape described below is what's on main today.

What it does

pgrdf.construct(q TEXT) → SETOF JSONB

For every solution of the WHERE clause, the SPARQL CONSTRUCT template is instantiated. Each instantiated triple is emitted as one JSONB row of shape:

json
{"subject": "<iri-or-bnode>", "predicate": "<iri>", "object": "<iri-or-bnode-or-literal>"}

This is directly pipeable into INSERT INTO ... SELECT, a Postgres VIEW, an INSERT DATA SPARQL UPDATE in another graph, or any tabular consumer.

Why you'd use it

  • groups Project managers — capture a subgraph as a row set you can hand to downstream tools. A snapshot, a report extract, or an ETL hand-off — one SQL call, no bespoke serialisation.
  • query_stats Data scientists — fold a graph-shaped feature into a Postgres VIEW. SELECT against the view in your existing pipelines. No leave-and-re-enter SPARQL.
  • school Ontologists — express the canonical "compute the shape, then assert it" pattern (the same one INSERT WHERE uses, but as a read with the result on the wire instead of in the graph).
  • code Backend engineers — read-only counterpart to the SPARQL UPDATE surface. Same template + WHERE pattern; result is rows, not a mutation.
  • settings Operators — observe what an INSERT WHERE would assert without committing.

Surface as it stands on main

The slice countdown reveals the order capabilities landed:

Phase D sliceFeature
59pgrdf.construct(q) foundation with constant-only templates (no template variables).
58Variable substitution in templates — ?s ?p ?o from the WHERE solution flows into the template.
57Blank-node templates with fresh-per-solution labels — _:b in the template becomes a distinct blank node per solution row.
56Multi-triple templates with shared per-solution bnodes — a _:b referenced twice in the template shares one identity per solution.
55GRAPH-scoped WHEREWHERE { GRAPH <iri> { ?s ?p ?o } } routes to a named graph partition.
54WHERE-shorthand form with W3C BGP restrictions — the CONSTRUCT WHERE { … } short form (template = WHERE pattern).

The remaining Phase D slices (53 → cut) close out tests, docs, spec sync, and the release tag.

Examples

Constant-only template

sql
-- "Always emit a marker triple, once per matching subject."
SELECT * FROM pgrdf.construct(
  'PREFIX ex: <http://example.com/>
   PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
   CONSTRUCT { ex:audit rdf:type ex:Marker }
   WHERE     { ?s ex:reviewed true }');
--  → {"subject": "http://example.com/audit",
--     "predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
--     "object": "http://example.com/Marker"}
--  (one row per matching ?s)

Variable substitution

sql
-- "Every Person becomes an Agent — read-only projection."
SELECT * FROM pgrdf.construct(
  'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
   PREFIX ex:   <http://example.com/>
   PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
   CONSTRUCT { ?p rdf:type ex:Agent }
   WHERE     { ?p rdf:type foaf:Person }');

Blank-node template — fresh per solution

sql
-- "For every person, mint a fresh contact node and attach their mailbox."
SELECT * FROM pgrdf.construct(
  'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
   PREFIX ex:   <http://example.com/>
   CONSTRUCT {
     ?p ex:contact _:c .
     _:c foaf:mbox ?m .
   }
   WHERE { ?p foaf:mbox ?m }');

The two occurrences of _:c in the template share a single identity per solution row — every solution gets a fresh _:c, but within one row the same _:c is referred to twice.

GRAPH-scoped WHERE

sql
-- "Snapshot the v3.7-staging graph."
SELECT * FROM pgrdf.construct(
  'CONSTRUCT { ?s ?p ?o }
   WHERE { GRAPH <http://example.org/v3.7-staging> { ?s ?p ?o } }');

WHERE-shorthand

sql
-- Template equals the WHERE pattern — the W3C "CONSTRUCT WHERE {…}"
-- short form. Restricted to BGPs (no OPTIONAL/UNION/etc. inside).
SELECT * FROM pgrdf.construct(
  'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
   CONSTRUCT WHERE { ?s foaf:name ?n }');

Composing with regular SQL

Because the result is a SETOF JSONB, you can fold a CONSTRUCT into a Postgres view, materialise it into a table, or pipe it into a SPARQL UPDATE in another graph:

sql
-- Materialise a CONSTRUCT into a relational table.
CREATE TABLE reports.person_agents AS
SELECT
    (t ->> 'subject')   AS person_iri,
    (t ->> 'predicate') AS predicate_iri,
    (t ->> 'object')    AS object_iri
  FROM pgrdf.construct(
    'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
     PREFIX ex:   <http://example.com/>
     PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
     CONSTRUCT { ?p rdf:type ex:Agent }
     WHERE     { ?p rdf:type foaf:Person }') AS t;

See also

  • SPARQL UPDATE — write counterpart with the same template-from-pattern shape.
  • GRAPH clause — the inner WHERE can be graph-scoped.
  • Roadmap — Phase D close-out and the Phase E / v0.5 forward edge.

Apache-2.0 licensed. Documentation for pgRDF — built with VitePress, served via GitHub Pages.