# Use contrib extensions

> Ported contrib modules registered as builtins (citext, hstore, ltree, pg_trgm, pgcrypto, pg_stat_statements, uuid-ossp, and others), CREATE EXTENSION against vendored control/SQL, and what remains unported.

- Repository: malisper/pgrust
- GitHub: https://github.com/malisper/pgrust
- Human docs: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21
- Complete Markdown: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21/llms-full.txt

## Source Files

- `crates/contrib/hstore/src/lib.rs`
- `crates/contrib/pg_stat_statements/src/lib.rs`
- `crates/contrib/pg_trgm/src/lib.rs`
- `crates/contrib/pgcrypto/src/lib.rs`
- `crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs`
- `vendor/postgres-18.3/share/extension/hstore.control`
- `crates/_support/seam/init/src/lib.rs`

---

---
title: "Use contrib extensions"
description: "Ported contrib modules registered as builtins (citext, hstore, ltree, pg_trgm, pgcrypto, pg_stat_statements, uuid-ossp, and others), CREATE EXTENSION against vendored control/SQL, and what remains unported."
---

pgrust installs contrib and PL modules through the same SQL surface as Postgres (`CREATE EXTENSION`, `LOAD`, `shared_preload_libraries`), but ported libraries never load as `.so` files. Each ported module registers a simple library name (`hstore`, `pgcrypto`, `uuid-ossp`, …) into the in-process builtin registry owned by `dfmgr_seams`; `load_external_function` / `load_file` resolve `$libdir/<name>` against that registry before any OS `dlopen`. Extension control files and install SQL live under the share tree’s `extension/` directory (clone default: `vendor/postgres-18.3/share/extension/`).

```mermaid
flowchart LR
  subgraph share["Share tree"]
    CTL["name.control\nmodule_pathname=$libdir/name"]
    SQL["name--version.sql\nCREATE FUNCTION … LANGUAGE C AS MODULE_PATHNAME"]
  end
  subgraph sql["SQL / utility"]
    CE["CREATE EXTENSION name"]
    LOAD["LOAD 'name' /\nshared_preload_libraries"]
  end
  subgraph runtime["postgres binary"]
    SEAMS["seams-init::init_all()\n→ crate::init_seams()"]
    REG["dfmgr_seams\nBUILTIN_LIBRARIES"]
    DFMGR["fmgr_dfmgr\nsimple_library_name + resolve"]
  end
  SEAMS --> REG
  CTL --> CE
  SQL --> CE
  CE --> DFMGR
  LOAD --> DFMGR
  DFMGR --> REG
```

## How loading works

1. At process startup, `seams-init` calls each ported crate’s `init_seams()`, which calls `dfmgr_seams::register_builtin_library` with a `BuiltinLibraryEntry` (`name`, symbol `lookup`, optional `pg_init`).
2. `CREATE EXTENSION` reads `<name>.control` from the extension control path (default: `$share/extension`), then runs the version SQL script with `MODULE_PATHNAME` substituted from `module_pathname` (normally `$libdir/<name>`).
3. Each `CREATE FUNCTION … LANGUAGE C AS 'MODULE_PATHNAME','sym'` call reaches `load_external_function`, which strips path/suffix to a simple name and hits the registry when present.
4. Optional `_PG_init` equivalents (`pg_init`) run once per backend on first load (`LOAD`, preload, or first symbol resolve)—for example pgcrypto’s `pgcrypto.builtin_crypto_enabled` GUC and `pg_stat_statements` hooks when preload is in progress.

Unregistered names fall through to the OS dynamic loader. The Rust backend does not expose a Postgres C ABI for arbitrary extensions, so generic third-party `.so` modules are not a supported path.

## Prerequisites

| Requirement | Detail |
| --- | --- |
| Running server | Built `postgres` binary with share dir wired (`PGRUST_PGSHAREDIR` at build, or `-L` at `--initdb` / boot). |
| Share tree | Must contain `extension/<name>.control` and matching `*.sql` scripts. Clone tree: `vendor/postgres-18.3/share`. |
| Superuser / trust | Same as Postgres control flags (`superuser`, `trusted`). |
| `pg_stat_statements` only | Must appear in `shared_preload_libraries` **before** postmaster start, then `CREATE EXTENSION`. |

`plpgsql` is installed automatically during `--initdb` (`CREATE EXTENSION plpgsql` in the post-bootstrap SQL). You do not need to create it by hand on a fresh cluster.

## Create a ported extension

<Steps>
<Step title="Confirm the share tree">
Point the cluster at a share directory that includes the extension control/SQL files. On a source build:

```bash
ls vendor/postgres-18.3/share/extension/*.control
```

Wrong or incomplete share trees cause missing-control or missing-script failures before any symbol load.
</Step>
<Step title="Connect and create">
```sql
CREATE EXTENSION hstore;
CREATE EXTENSION citext;
CREATE EXTENSION pg_trgm;
CREATE EXTENSION pgcrypto;
CREATE EXTENSION ltree;
CREATE EXTENSION "uuid-ossp";
```

Quoted identifiers matter for hyphenated names (`uuid-ossp`). Default versions come from each `.control` file (for example hstore `1.8`, citext `1.8`, pg_trgm `1.6`, pgcrypto `1.4`, ltree `1.3`, uuid-ossp `1.1`).
</Step>
<Step title="Verify">
```sql
\dx
SELECT 'a' || hstore('k','v');  -- after CREATE EXTENSION hstore
SELECT similarity('cat', 'cats');  -- after CREATE EXTENSION pg_trgm
SELECT digest('x', 'sha256');  -- after CREATE EXTENSION pgcrypto
```

`\dx` lists installed extensions. Function calls that resolve symbols exercise the builtin registry (not a filesystem `.so`).
</Step>
</Steps>

### `pg_stat_statements` (preload required)

```text
# postgresql.conf (or -c on the server command line)
shared_preload_libraries = 'pg_stat_statements'
```

Restart the postmaster so `_PG_init` runs while `process_shared_preload_libraries_in_progress` is true (hooks + shmem). Then:

```sql
CREATE EXTENSION pg_stat_statements;
SELECT * FROM pg_stat_statements LIMIT 5;
```

Calling the view without preload yields the same class of error as Postgres: *pg_stat_statements must be loaded via "shared_preload_libraries"*. Catalog objects can still be created if you only run `CREATE EXTENSION` without preload; tracking will not be active.

### Logical decoding plugin (`test_decoding`)

`test_decoding` is not a `CREATE EXTENSION` module. It registers as a **builtin output plugin** (`register_builtin_output_plugin`, name `test_decoding`). Use it where Postgres expects a plugin library name on a logical decoding slot (for example `pg_create_logical_replication_slot(..., 'test_decoding')`). Dispatch goes through the builtin output-plugin path, not the library-symbol table.

## Ported modules inventory

### CREATE EXTENSION–ready (control + SQL in share, builtin library registered)

| Extension | Builtin library name | Default version (control) | Crate | Notes |
| --- | --- | --- | --- | --- |
| `citext` | `citext` | `1.8` | `crates/contrib/citext` | Case-insensitive text; type I/O reuses core `text`. |
| `hstore` | `hstore` | `1.8` | `crates/contrib/hstore` | Scalar + GiST/GIN ops ported; subscript handler not wired. |
| `ltree` | `ltree` | `1.3` | `crates/contrib/ltree` | Scalar path ops; btree/hash OK; GiST support symbols are loud stubs. |
| `pg_trgm` | `pg_trgm` | `1.6` | `crates/contrib/pg_trgm` | Similarity + most GIN/GiST strategies; regexp NFA strategies unported. |
| `pgcrypto` | `pgcrypto` | `1.4` | `crates/contrib/pgcrypto` | Digest/HMAC/crypt/PGP suite; GUC `pgcrypto.builtin_crypto_enabled`. |
| `pg_stat_statements` | `pg_stat_statements` | `1.12` | `crates/contrib/pg_stat_statements` | Requires `shared_preload_libraries`. |
| `uuid-ossp` | `uuid-ossp` | `1.1` | `crates/contrib/uuid_ossp` | Generators only; core `uuid` type is separate. |
| `injection_points` | `injection_points` | `1.0` | `crates/contrib/injection_points` | Test/harness extension; stats helpers raise not-supported. |
| `plpgsql` | `plpgsql` | `1.0` | `crates/pl/plpgsql/...` | Auto-created at initdb; not a contrib crate under `crates/contrib`. |

Control files set `module_pathname = '$libdir/<name>'` (and trust/relocatable flags). Example (`hstore.control`):

```text
comment = 'data type for storing sets of (key, value) pairs'
default_version = '1.8'
module_pathname = '$libdir/hstore'
relocatable = true
trusted = true
```

### Builtin library, no vendored extension control in clone share

| Module | Library / registry | Status |
| --- | --- | --- |
| `pg_prewarm` | `pg_prewarm` | `pg_prewarm()` ported and registered. `autoprewarm_*` symbols are loud stubs. No `pg_prewarm.control` / install SQL under `vendor/postgres-18.3/share/extension/` in this tree—`CREATE EXTENSION pg_prewarm` is not available from the vendored share set. |
| `test_decoding` | output-plugin name `test_decoding` | Logical decoding only (see above). |
| `regress` | `regress` | Regression-helper library for the test suite, not a user extension. |

### Partial / non-extension ports under `crates/contrib`

| Crate | What exists | What is missing for SQL use |
| --- | --- | --- |
| `earthdistance` | Great-circle distance math (`geo_distance_internal`) | No `register_builtin_library`, no control/SQL, not wired in `seams-init::init_all`. |
| `amcheck` (`verify_nbtree`, `verify_heapam`, …) | Verifier logic installed via seams for internal/SQL entry points as ported | No `amcheck.control` in share; not a full contrib extension package in this tree. |

### Known feature gaps inside otherwise-installable modules

| Module | Gap | Failure shape |
| --- | --- | --- |
| `hstore` | `hstore_subscript_handler` | Explicit “not yet wired” error. |
| `pg_trgm` | Regexp / case-insensitive regexp index strategies (`trgm_regexp` NFA) | Explicit “unported” error on those strategies; other strategies work. |
| `ltree` | `gist_ltree_ops` / `gist__ltree_ops` support functions | Symbols present for `CREATE EXTENSION` validation; using GiST indexes panics until generic GiST extproc keystone is complete. |
| `pg_prewarm` | `autoprewarm_start_worker`, `autoprewarm_dump_now` | Loud unported errors if called. |
| `injection_points` | Per-point / fixed statistics | “not ported” errors on stats SQL functions. |

## Share directory layout

Clone-oriented share (what `--initdb -L` / `PGRUST_PGSHAREDIR` should see for contrib install scripts):

:::files
vendor/postgres-18.3/share/
├── extension/
│   ├── citext.control + citext--*.sql
│   ├── hstore.control + hstore--1.8.sql
│   ├── ltree.control + ltree--*.sql
│   ├── pg_trgm.control + pg_trgm--*.sql
│   ├── pgcrypto.control + pgcrypto--*.sql
│   ├── pg_stat_statements.control + pg_stat_statements--*.sql
│   ├── uuid-ossp.control + uuid-ossp--*.sql
│   ├── injection_points.control + injection_points--1.0.sql
│   └── plpgsql.control + plpgsql--1.0.sql
├── postgres.bki
├── system_*.sql
└── timezone/ …
:::

Several crates also keep a copy of control/SQL under `crates/contrib/<mod>/extension/` (for example hstore, citext, pg_trgm). Runtime install uses the **share** tree the binary was built/bootstrapped with, not the crate path.

<Note>
Docker images may install a full PGDG PostgreSQL share tree for bootstrap templates and `psql`. That tree can list many more extension control files than pgrust has ported. Presence of a `.control` file does **not** mean the module is a pgrust builtin—only the inventory above is backed by `register_builtin_library` / output-plugin registration.
</Note>

## Runtime configuration that interacts with contrib

| Setting | Role |
| --- | --- |
| Share path (`PGRUST_PGSHAREDIR`, initdb `-L`) | Locates `extension/*.control` and scripts. |
| `shared_preload_libraries` | Required for `pg_stat_statements` hooks/shmem. |
| `LOAD 'name'` | Runs builtin `pg_init` once if registered (for example pgcrypto GUCs). |
| `pgcrypto.builtin_crypto_enabled` | `on` (default) / `off` / `fips`; `off` disables built-in crypto entry points. |
| `pg_trgm.similarity_threshold` and related | Registered from pg_trgm `pg_init` (defaults match Postgres: 0.3 / 0.6 / 0.5). |
| `io_method=sync`, stack GUCs | Server-wide pgrust requirements; not extension-specific but required for a stable session. |

## What remains unported

- **Generic Postgres extensions** (any `$libdir` module without a pgrust builtin registration), including PL/Python, PL/Perl, PL/Tcl and typical third-party C extensions—README status: not generally compatible.
- **Full upstream contrib set**: only the modules in the inventory tables are present under `crates/contrib` / plpgsql. Everything else is absent as a port (no registry entry).
- **In-module stubs** listed above (hstore subscripts, pg_trgm regexp NFA, ltree GiST, autoprewarm, injection_points stats).
- **Extension admin SRFs / some ALTER paths** still deferred in `backend-commands-extension` (for example `pg_available_extensions`, parts of `ALTER EXTENSION … ADD/DROP`). Day-to-day `CREATE EXTENSION` for the listed modules uses the ported control + script pipeline.

## Troubleshooting

| Symptom | Likely cause | What to check |
| --- | --- | --- |
| `could not open extension control file` / extension does not exist | Share path wrong or control not in that tree | `-L` / `PGRUST_PGSHAREDIR`; `ls $share/extension/*.control` |
| `could not find function "…" in file "$libdir/…"` | Symbol missing from builtin `lookup`, or library not registered | Confirm module is in the CREATE EXTENSION–ready table; rebuild so `seams-init` includes the crate |
| Dynamic loader / missing `.so` errors for a random extension | Module not ported; fall-through to OS loader | Only use the inventory above; third-party C extensions are out of scope |
| `pg_stat_statements must be loaded via "shared_preload_libraries"` | Preload not set or postmaster not restarted | Set GUC, full restart, then `CREATE EXTENSION` |
| Extension creates but GiST/ltree index or regexp trgm fails | Known port gap | Use sequential scan / non-regexp strategies / btree for ltree until those paths land |
| `hstore` subscript errors | Subscript handler gap | Avoid `hstore` subscripting syntax; use operators/functions |
| Crypto functions refuse to run | `pgcrypto.builtin_crypto_enabled = off` | Reset GUC to `on` |

## Related pages

<CardGroup>
  <Card title="Seams and builtin libraries" href="/seams-and-builtins">
    Registry model, `register_builtin_library`, and how dfmgr replaces dlopen.
  </Card>
  <Card title="Install paths and share directory" href="/install-paths">
    `PGRUST_PGSHAREDIR`, `-L`, and why a wrong share tree breaks control/SQL lookup.
  </Card>
  <Card title="Compatibility and status" href="/compatibility">
    Extension/PL boundaries, non-production status, and regression-oracle model.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Broader failure modes including unported extension loads and boot settings.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    Runtime GUCs including stack, `io_method`, and path env vars.
  </Card>
</CardGroup>
