# Compatibility

> Which PostgreSQL versions, which Go versions, and what stability means here.

Source: https://ormgo.vercel.app/en/docs/compatibility/
Symbols: https://ormgo.vercel.app/api/orm.txt — the generated list of every exported name.

---
## PostgreSQL

**14, 15, 16, 17 and 18.**

Not "should work on". The compatibility suite refuses to run unless all five are reachable at once:

```text
the compatibility matrix requires every supported major and
[ORM_TEST_DSN_PG14 ... PG18] are unset. Skipping here would report a five-major
claim proven by however many servers happened to be running
```

A version listed as supported and never run against is a claim nobody should believe.

14 leaves the list when upstream ends its support on 12 November 2026.

## What is proved across all five

- The whole user workflow: migrate, generate, check, write, read, join, refresh.
- **Byte-identical artifacts.** Generated Go, `orm.lock` and migration artifacts are the same on 14 and on 18. A mixed-server team gets no diff on checkout.
- No server-local content in any artifact: no OIDs, no database name, no server version, no deparsed definition, no absolute paths.

## Go

The floor is the version in `go.mod`, currently **1.24**, and raising it is a decision rather than a side effect of a newer toolchain being available. A dedicated CI job pins `GOTOOLCHAIN=local` and builds only the modules that are on the floor, so the claim is proved rather than assumed.

Some peripheral modules declare a higher version because their own dependencies do — the Testcontainers helper and some examples need 1.25. That does not move the library's floor, and the jobs are separated so it cannot.

## PostGIS

Proved on the combinations the project actually claims: PostgreSQL 17 with PostGIS 3.5, 16 with 3.4, and 14 with 3.4. The spatial suite skips when the extension is unavailable — right on a developer's machine, wrong in CI — so CI sets `ORM_REQUIRE_POSTGIS=1`, which turns the skip into a failure.

## Stability

The public API is frozen at v1 and tracked by a generated manifest. A removed symbol, a changed signature, a tightened constraint or a method added to an interface consumers implement all fail the build. The manifest tool is itself tested for noticing each of those.

## Extensions

`citext`, `hstore`, `pg_trgm`, `uuid-ossp` and PostGIS are recognised when present. None is required, and the ORM never creates an extension — that is a privileged operation belonging to whoever owns the database.

## Worked examples

### A CI matrix that cannot quietly shrink

```yaml
strategy:
  fail-fast: false
  matrix:
    postgres: ['14', '15', '16', '17', '18']
```

And the test that refuses to run on fewer, rather than reporting a five-version
claim proven by however many servers happened to be up:

```go
func requireEveryMajor(t *testing.T) map[string]string {
    var missing []string
    for _, v := range []string{"14", "15", "16", "17", "18"} {
        if os.Getenv("PG_DSN_"+v) == "" {
            missing = append(missing, v)
        }
    }
    if len(missing) > 0 {
        t.Fatalf("missing servers for %v; skipping here would report a claim "+
            "nobody proved", missing)
    }
    return nil
}
```

### Pinning the Go floor and proving it

```yaml
- name: the library builds on its declared floor
  env:
    GOTOOLCHAIN: local     # do not silently fetch a newer Go
  run: go build ./...
```

Without `GOTOOLCHAIN=local`, a newer toolchain is fetched on demand and the floor
is never tested.

### Making an optional extension mandatory in CI

```yaml
- env:
    ORM_REQUIRE_POSTGIS: '1'
  run: go test ./postgis/...
```

The spatial suite skips when PostGIS is absent, which is right on a laptop and
wrong in CI. The variable turns the skip into a failure.
