# 06 — Installation

There are three supported install patterns, in order of preference.

## 6.1 PG 18+ — GUC-based drop-in (forward path, INSTALL spec §7)

A postgres:18+ container starts with two GUCs pointed at a directory
containing the pre-built extension:

```bash
postgres \
  -c shared_preload_libraries=pgrdf \
  -c extension_control_path=/pgrdf/share/extension:$system \
  -c dynamic_library_path=/pgrdf/lib:$libdir
```

No image rebuild, no entrypoint wrappers, no init scripts. This is
the spec-preferred forward path; pgRDF will switch to it once pgrx
ships a stable 0.17+/0.18 line that builds on current rustc (see
ERRATA E-006, E-007). Until then v0.3 pins to PG 17 + pgrx 0.16.1
and uses the per-file bind-mount pattern below.

## 6.2 PG 14–17 — file placement (INSTALL spec §4.3)

The GUCs above don't exist on PG ≤ 17, so the .so / .control / .sql
files must end up at canonical paths (`$libdir` and
`$sharedir/extension`). Two supported flavours:

**Local compose (what `compose/compose.yml` implements):** stock
`postgres:17.4-bookworm` boots with per-file bind mounts placing the
locally-built artifacts directly at `/usr/lib/postgresql/17/lib/` and
`/usr/share/postgresql/17/extension/`. No image rebuild, no
entrypoint wrapper, no init script — the spec-compliant local
incarnation of "drop-in extension files".

**K8s reference manifest (INSTALL spec §5):** a Kubernetes init
container fetches the release tarball into an `emptyDir`, and the
postgres container's command wrapper does:

```bash
cp -f /pgrdf/lib/*.so /usr/lib/postgresql/${PG_MAJOR}/lib/
cp -f /pgrdf/share/extension/* /usr/share/postgresql/${PG_MAJOR}/extension/
exec docker-entrypoint.sh postgres -c shared_preload_libraries=pgrdf
```

The corresponding entrypoint-wrapper container is out of scope for
v0.3 local dev; if you must reproduce an entrypoint-copy layout
locally, use `cargo pgrx run pg17` — pgrx handles file placement.

## 6.3 Container with the extension baked in

Explicitly rejected by INSTALL §10. Couples our release cadence to the
postgres image rebuild cycle.

## CREATE EXTENSION

Once the files are in place and Postgres is running:

```sql
CREATE EXTENSION pgrdf;
SELECT pgrdf.version();   -- → '0.3.0'
```

The extension declares `superuser = true` because we manipulate
`shared_preload_libraries` and (in later phases) create background
workers.

## Upgrade between v0.x versions

**pgRDF v0.x reserves the right to break schema and UDF signatures
between minor releases.** There is no in-place upgrade path.
`ALTER EXTENSION pgrdf UPDATE` is not supported in v0.x and is
deferred until v1.0.

### Upgrade procedure

To move from one v0.x version to the next:

1. **Export your data** while still on the old version. v0.3 has no
   `CONSTRUCT` UDF, so dump the dictionary-decoded quads directly via
   SQL — per graph:

   ```sql
   SELECT s.lexical_value AS subject,
          p.lexical_value AS predicate,
          o.lexical_value AS object
   FROM pgrdf._pgrdf_quads q
   JOIN pgrdf._pgrdf_dictionary s ON s.id = q.subject_id
   JOIN pgrdf._pgrdf_dictionary p ON p.id = q.predicate_id
   JOIN pgrdf._pgrdf_dictionary o ON o.id = q.object_id
   WHERE q.graph_id = <YOUR_GRAPH>;
   ```

   Serialise the result to Turtle externally (psycopg → `oxttl`,
   `rdflib`, etc.). A future release is expected to add a
   `pgrdf.construct_turtle(…)` UDF for direct Turtle output (see
   [`specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md`](../specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md)
   §6); until that lands the SQL dump above is the supported path.

2. **Drop the extension**:

   ```sql
   DROP EXTENSION pgrdf CASCADE;
   ```

   `CASCADE` is required because the partitioned `_pgrdf_quads` table
   and the SPO/POS/OSP indexes depend on the extension.

3. **Install the new version** per §6.1 / §6.2 above.

4. **Re-create + re-load**:

   ```sql
   CREATE EXTENSION pgrdf;
   SELECT pgrdf.add_graph(<YOUR_GRAPH>);
   SELECT pgrdf.load_turtle('/path/to/your/export.ttl', <YOUR_GRAPH>);
   ```

### Why no in-place upgrade?

- Pre-1.0 schema is fluid. Anticipated additions (named-graph
  scoping, an IRI ↔ `graph_id` mapping table per LLD v0.4-FUTURE §3,
  CONSTRUCT, SPARQL UPDATE) require migrations the v0.3 schema can't
  describe ahead of time.
- The dictionary `id` space is not stable across versions — the same
  Turtle re-ingested under a different build gets different integer
  ids.
- The `is_inferred` boolean column on `_pgrdf_quads` may grow or
  shift semantics as the OWL 2 RL surface evolves.

Once v1.0 cuts, `ALTER EXTENSION pgrdf UPDATE` migrations will land
alongside a frozen on-disk schema. No date is committed yet.

### Cluster-managed installations

Operators running pgRDF on top of CloudNativePG, StackGres, Apache
AGE, or similar:

- Schedule the upgrade as a planned maintenance window — `DROP
  EXTENSION pgrdf CASCADE` is destructive to pgRDF data, even though
  surrounding Postgres tables are untouched.
- Snapshot the Postgres volume before `DROP EXTENSION`.
- Test on a staging database first; verify the dump-and-reload
  round-trip produces the expected triple count.
- File issues at <https://github.com/styk-tv/pgRDF/issues> if the
  upgrade friction is high — we want to smooth this path before v1.0.

## Verifying conformance (INSTALL spec §12)

After install, run the conformance checklist:

```bash
psql -c "SELECT extversion FROM pg_extension WHERE extname='pgrdf';"   # ≡ Cargo.toml version
psql -c "\dx pgrdf"                                                     # extension present
psql -c "SHOW shared_preload_libraries;"                                # contains pgrdf
```

All three are wired into `tests/regression/sql/00-smoke.sql` so this
is gated in CI.
