Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
61 changes: 47 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ decision that already has an ADR.
| `docs/plans/*.md` | Per-effort plans (performance, bench module, compat suites). |
| `docs/jdbc-conformance-matrix.md`, `docs/pooler-compatibility.md` | Behavior matrices. |
| `docs/api-surface.md` | The enumerated public API surface (ADR-0020), enforced by `ApiSurfaceManifestTest`. A new public type fails the build until it is listed as Stable or Experimental. |
| `docs/static-analysis.md` | Which analysers the build runs and why, which were rejected, and the scope agreed for the ones not yet adopted. |
| `docs/life-of-a-query.md` | Best single orientation doc for the core execution path. |
| `docs/reviews/`, `docs/benchmarks/` | Findings from past audits; benchmark reference numbers. |
| `compat-suites/` | pgjdbc and Hibernate upstream suites run against our driver, with committed baselines. |
Expand Down Expand Up @@ -111,21 +112,50 @@ PgBouncer, auth, unix socket) provision their own containers and skip entirely
when `pg.it.host` is set. `scripts/run-integration-matrix.sh` sweeps server
versions; ADR-0004 defines the supported range (9.1-18; PRs gate on 14-18).

### Build gates (these fail the build at `verify`, not at `test`)

Run `./mvnw verify` before declaring a change done. The gates are:

- **Spotless** (`palantirJavaFormat`, 4-space). Fix with `./mvnw spotless:apply`
before committing. Formatting is not checked by `mvn test`, so it is the most
common late surprise.
- **`AsciiSourcePolicyTest`** (in `postgresql-client`, runs in the `test` phase):
every file under any module's `src/`, everything under `docs/`, and the root
`README.md` must be 7-bit ASCII. No em-dashes, curly quotes, or arrows.
### Build gates

Run `./mvnw verify` before declaring a change done. They do not all fire at the
same phase, which is worth knowing when a build fails early: the compiler gates
fail at `compile` and the source-policy tests at `test`, so a plain `mvn test`
already enforces them. Spotless and JaCoCo wait for `verify`, which is what makes
formatting the most common late surprise.

**In the compiler** (configured in the root `pom.xml`, so every build gets them):

- **`-Xlint:all,-this-escape` under `-Werror`**: any javac warning fails the
build. `this-escape` is the sole exclusion.
- **NullAway at ERROR** over `org.postgresql.client.protocol` and
`org.postgresql.client.core`: a nullness violation there fails the compile.
Those prefixes are listed in `NullAway:AnnotatedPackages` in the root POM, so
everything under them is non-null by default; mark the exceptions with
jspecify's `@Nullable`. Error Prone is present only as NullAway's carrier, with
its own checks disabled on purpose -- do not "fix" the `-XepDisableAllChecks`
flag. See `docs/static-analysis.md`.

**Source-policy tests** (plain unit tests, so they run in the `test` phase):

- **`AsciiSourcePolicyTest`** (in `postgresql-client`): every file under any
module's `src/`, everything under `docs/`, and the root `README.md` must be
7-bit ASCII. No em-dashes, curly quotes, or arrows.
- **`NoSynchronizedSourcePolicyTest`** (in `postgresql-client`): the `synchronized`
keyword is banned outright across the four shipped modules, per ADR-0001. Use
`ReentrantLock`. A use with genuinely no I/O beneath it goes in that test's
`ALLOWED` set with its reason, which keeps it a reviewed exception.
- **`ApiSurfaceManifestTest`** (in `postgresql-client`): every public type in an
exported package must be listed in `docs/api-surface.md` as Stable or
Experimental.
- **`ModuleLayeringTest`** (in `postgresql-client-jdbc`): `postgresql-client-jdbc` and
`postgresql-client-pgjdbc-compat` must not import `org.postgresql.client.protocol.*`
directly; they reach it only transitively through core.
- **JaCoCo floor**: 20% line and branch per module bundle. A tripwire, not a target.
- **Enforcer**: Java/Maven minimums, dependency convergence, reactor convergence.

**Later phases:**

- **Enforcer** (`validate`): Java/Maven minimums, dependency convergence, reactor
convergence.
- **Spotless** (`verify`; `palantirJavaFormat`, 4-space). Fix with
`./mvnw spotless:apply` before committing.
- **JaCoCo floor** (`verify`): 20% line and branch per module bundle. A tripwire,
not a target.

### Test conventions

Expand Down Expand Up @@ -217,11 +247,14 @@ commit. See each suite's `README.md`.

## Common pitfalls

- Formatting and ASCII failures only appear at `verify`; run
`./mvnw spotless:apply` before committing and avoid pasting Unicode punctuation.
- Formatting failures only appear at `verify`; run `./mvnw spotless:apply` before
committing. ASCII failures surface earlier, at `test`, so avoid pasting Unicode
punctuation into source, docs, or commit messages.
- A new public package without a `module-info.java` export compiles in-module
and breaks downstream.
- `synchronized` around blocking I/O pins virtual threads: use `ReentrantLock`.
`NoSynchronizedSourcePolicyTest` rejects the keyword in shipped code, so this
fails the build rather than showing up later as a latency mystery.
- Do not let pgjdbc's legacy behavior leak into core; it belongs in the JDBC and
compat layers. Core changes for compat parity need a real core-level reason.
- `-Dtest=...` across the reactor fails modules that lack the class unless you
Expand Down
74 changes: 36 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,33 @@ implemented on top of it. The test suite includes integration tests that run
against real PostgreSQL (and PgBouncer) via Testcontainers. APIs are still
evolving and there are no compatibility guarantees yet. The implementation
roadmap (phased, commit-by-commit) lives in
[docs/plans/overall.md](docs/plans/overall.md), which also defines the
"first useful JDBC release" minimum.
[docs/plans/overall.md](docs/plans/overall.md).

## Connecting

The JDBC layer registers itself for the `jdbc:pg:` scheme (ADR-0015), and takes
libpq-style parameters:
The PostgreSQL-native API is the product; nothing in it mentions `java.sql`.
`PgConnections.connect` returns a `PgConnection`, and `execute` returns a pull
cursor over the rows:

```java
PgConnectionConfig config = PgConnectionConfig.builder()
.host("localhost", 5432)
.database("appdb")
.user("app")
.password(secret)
.sslMode(SslMode.VERIFY_FULL)
.build();

try (PgConnection connection = PgConnections.connect(config);
PgResultStream rows = connection.execute("select id from t where name = $1", List.of("widget"))) {
while (rows.next()) {
System.out.println(rows.currentRow().getLong(1));
}
}
```

The JDBC layer sits on top of that and registers itself for the `jdbc:pg:`
scheme (ADR-0015), taking libpq-style parameters:

```java
String url = "jdbc:pg://localhost:5432/appdb?user=app&sslmode=verify-full";
Expand All @@ -56,9 +76,7 @@ try (Connection connection = DriverManager.getConnection(url, "app", secret);

It also answers to `jdbc:postgresql:` for ported applications, though which
driver claims that scheme when real pgjdbc is also on the path is a decision of
its own (ADR-0015). The PostgreSQL-native API underneath it is
`org.postgresql.client.core.PgConnections.connect(...)`, which returns a
`PgConnection` and never mentions `java.sql`.
its own (ADR-0015).

## Project layout

Expand All @@ -85,20 +103,14 @@ modules together. The modules are:
place, but some `PGConnection` methods are still stubbed and several functional
paths await live-server verification. Treat the "drop-in" goal conservatively
until that ring fully lands (see `docs/follow-up.md`, N4.3).
- **`postgresql-client-bench`** - A driver-agnostic, pgbench-style JDBC benchmark that
measures the driver, not the database. The driver under test (pg-java or pgjdbc)
is supplied at runtime, never bundled. Build-only; not published. See
`postgresql-client-bench/README.md`.
- **`postgresql-client-bench-jmh`** - Server-free JMH micro-benchmarks for the codec,
framing and buffer paths, where allocation (bytes/op) is the tracked number. See
`docs/benchmarks/` for the reference runs. Build-only; not published.
- **`postgresql-client-coverage`** - Aggregates per-module JaCoCo coverage into one
combined report. Build-only; not published.
- **`postgresql-client-native-smoke`** - A GraalVM native-image smoke gate: it compiles the
native-image metadata shipped by `postgresql-client-jdbc` and `postgresql-client-pgjdbc-compat` into
a native binary and runs it, catching metadata regressions. Off by default (a
plain build only compiles the probe); the native build runs under the
`native-smoke` profile on a GraalVM JDK. Build-only; not published.
Four more modules are build-only and never published: **`postgresql-client-bench`**
(a driver-agnostic, pgbench-style JDBC benchmark that measures the driver, not the
database; see `postgresql-client-bench/README.md`),
**`postgresql-client-bench-jmh`** (server-free JMH micro-benchmarks where
allocation is the tracked number; reference runs in `docs/benchmarks/`),
**`postgresql-client-coverage`** (aggregated JaCoCo report), and
**`postgresql-client-native-smoke`** (a GraalVM native-image gate that builds and
runs a binary from the shipped metadata, under the `native-smoke` profile).

## Requirements

Expand Down Expand Up @@ -131,23 +143,9 @@ stays Docker-free. They require a running Docker daemon:
mvn verify -Pintegration-tests
```

By default the integration tests run against `postgres:17`; pass
`-Dpg.it.image=<image>` to test another server image, or use
`scripts/run-integration-matrix.sh` to sweep several PostgreSQL versions.

To run the shared-server integration tests against an already-running
PostgreSQL instead of Docker, set `-Dpg.it.host`:

```sh
mvn verify -Pintegration-tests -Dpg.it.host=localhost
```

The remaining coordinates default to port `5432` and `postgres` for the user,
password, and database; override them with `-Dpg.it.port`, `-Dpg.it.user`,
`-Dpg.it.password`, and `-Dpg.it.database`. The special-purpose harnesses
(TLS, PgBouncer, auth, unix socket) provision their own containers and are
skipped entirely when `pg.it.host` is set, so an external-server run never
touches Docker.
They default to `postgres:17`. Selecting another image, sweeping the version
matrix, and pointing the suite at an already-running server instead of Docker
are covered in [AGENTS.md](AGENTS.md).

## Contributing

Expand Down
9 changes: 9 additions & 0 deletions docs/adr/ADR-0002-module-boundaries-and-dependency-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ JDBC module.
versions.
- JDBC ergonomics never dictate core API shape; the JDBC layer is a thin adapter.
- Optional integrations cannot pull weight into core.

## Amendments

- **Protocol types ride on core's public surface (pre-1.0, N7.3).** Core declares
`requires transitive org.postgresql.client.protocol` and the codec SPI names
`PgWriteBuffer` and `ByteSlice`, so a caller writing a codec compiles against
protocol types. The layering rule above is unaffected -- nothing flows upward --
but "protocol is an implementation detail of core" is not true of the exported
API. Decide before the API freeze whether to bless that or unweld it.
7 changes: 5 additions & 2 deletions docs/adr/ADR-0003-testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ A layered test strategy combining unit tests, ground-truth fixtures, a mock
server, and a gated integration matrix.

- Unit tests (JUnit 5) in each module; protocol/orchestration tests use in-memory
transports and ground-truth golden fixtures captured from libpq/Wireshark/psql
(not hand-derived from the same reasoning that wrote the encoder).
transports and golden fixtures that are independent of the encoder (not
hand-derived from the same reasoning that wrote it). The shipped fixtures are
spec-derived, built by hand from the message-format documentation; a
capture-based libpq/Wireshark/psql corpus is the stronger ground truth and
remains aspirational (N7.3).
- A programmable mock PostgreSQL server (test-only) drives auth edge cases,
protocol-version negotiation, protocol violations, and server-closes-mid-query.
- Integration tests use Testcontainers plus a PostgreSQL image via the Failsafe
Expand Down
3 changes: 3 additions & 0 deletions docs/adr/ADR-0013-pooler-and-proxy-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ transaction pooling, with CI coverage where feasible.
- The driver works correctly behind transaction poolers when configured for it,
instead of silently relying on session state that the pooler does not preserve.
- Feature availability under pooling is documented, not discovered in production.
- Pooled logical handles (`ConnectionPoolDataSource`) do not reset session state
between handles. That is pgjdbc parity and a documented contract, not a gap to
fix: an application that changes session state on a pooled handle must undo it.
- How rich the pooler-compatible mode should be (which features to actively police
versus merely document) remains an open question to revisit.
5 changes: 3 additions & 2 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

This directory records the architectural decisions for pg-java as individual,
numbered ADRs. Each ADR captures one decision: its context, the decision itself,
and the consequences. The implementation plan (`docs/plans/overall.md`) carries a
summary of each decision inline; these files are the canonical, citable record.
and the consequences. These files are the canonical, citable record; the
implementation plan (`docs/plans/overall.md`) cites them rather than restating
them.

ADRs are append-only in spirit: once accepted, an ADR is superseded by a new one
rather than edited away. A decision that survives but whose details have moved on
Expand Down
3 changes: 3 additions & 0 deletions docs/benchmarks/micro-bytes-per-op-001.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Micro-benchmark bytes/op reference (run 001)

**Superseded by [run 002](micro-bytes-per-op-002.md)**, which is the tracked reference. Kept as
the first committed capture.

Per-operation heap allocation (`gc.alloc.rate.norm`, **bytes/op**) for the driver's server-free
hot paths, from the JMH micro-harness (`pg-java-bench-jmh`, N5.1). This is the tracked allocation
KPI for the write-side work (N5.7): unlike throughput, **allocation is counted, not sampled**, so
Expand Down
3 changes: 3 additions & 0 deletions docs/benchmarks/pgjava-vs-pgjdbc-001.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Benchmark: pg-java vs pgjdbc (run 001)

**Superseded by [run 002](pgjava-vs-pgjdbc-002.md)**, which re-ran this baseline after the
compat batch-rewrite pass-through was fixed. Kept as the first data point.

First `pg-java-bench` comparison of pg-java against real pgjdbc. This is the initial
data point toward the N5 exit criterion ("a committed, reproducible benchmark report
shows parity-or-better on every measured path"). It measures the driver, not the
Expand Down
3 changes: 3 additions & 0 deletions docs/benchmarks/pgjava-vs-pgjdbc-005.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Benchmark: pg-java vs pgjdbc (run 005) -- batch-size sweep, repeated

**Rankings superseded by [run 006](pgjava-vs-pgjdbc-006.md)**, which re-ran them on a fixed
harness. The sweep's shape and method below still stand.

A longer, repeated **batch-size sweep** to firm up run 004's single-shot numbers.
`batch-insert` across batch sizes **1, 10, 100, 500, 1000**, with batch-rewrite both **off**
and **on**, for all three driver surfaces, **`--repeat 3`** per cell. Run from the new sweep
Expand Down
4 changes: 4 additions & 0 deletions docs/benchmarks/pgjava-vs-pgjdbc-014.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Benchmark: pg-java vs pgjdbc (run 014) -- wide rows, NULL-heavy rows, and a compat-layer finding

**Compat cells superseded by [run 015](pgjava-vs-pgjdbc-015.md)**: they were measured against a
stale jar (the harness bug is documented in 015). The native cells and the two new workloads
stand as measured.

Two workloads the suite never had (`wide-row`, `null-heavy`, added this run) plus `select-rows` and
`batch-insert` as controls. The question they answer: everything measured before reads four to nine
columns per row, which is where a driver's column-lookup strategy, row reuse and lazy decode barely
Expand Down
Loading
Loading