cratefield-adapter-postgres 0.3.5

Database port over sqlx Postgres for the Cratefield harness native runtime (ADR 0004, issue #18)
Documentation
<p align="center">
  <a href="https://github.com/Cratefield/harness">
    <img src="https://raw.githubusercontent.com/Cratefield/harness/main/assets/banners/cratefield-adapter-postgres.png" alt="cratefield-adapter-postgres — Database, at size." width="100%">
  </a>
</p>

<p align="center">
  <a href="https://crates.io/crates/cratefield-adapter-postgres"><img src="https://img.shields.io/crates/v/cratefield-adapter-postgres.svg?style=flat-square&labelColor=0A0A0B&color=4C6FFF" alt="cratefield-adapter-postgres on crates.io"></a>
  <a href="https://docs.rs/cratefield-adapter-postgres"><img src="https://img.shields.io/docsrs/cratefield-adapter-postgres?style=flat-square&labelColor=0A0A0B&color=EDEBE6" alt="cratefield-adapter-postgres documentation"></a>
  <a href="https://github.com/Cratefield/harness/blob/main/LICENSE"><img src="https://img.shields.io/badge/LICENSE-MIT-4C6FFF?style=flat-square&labelColor=0A0A0B" alt="MIT"></a>
</p>

# cratefield-adapter-postgres

The [`Database`] port over `sqlx` Postgres 16 for the [Cratefield
harness](https://github.com/Cratefield/harness) native runtime (ADR 0004,
issue #18). **Native only** — the crate fails compilation on any wasm
target with a clear message, and the sqlx dependency is target-gated to
non-wasm builds, so it can never slip into a Worker.

```rust
use cratefield_adapter_postgres::Postgres;

let db = Postgres::connect("postgres://user:pass@host:5432/venture").await?;
db.apply_harness_migrations(&harness).await?;   // see below
// … through Arc<dyn Database> as with any adapter
```

- `Postgres::connect(url)` opens a pool (`sqlx` 0.8, `runtime-tokio`,
  `tls-rustls`); `batch` runs all statements in one transaction (atomic).
- Port statements arrive in the portable `?`-placeholder form rendered by
  sea-query's `SqliteQueryBuilder`; the adapter rewrites them to `$n`
  (skipping string literals, quoted identifiers and comments) and binds
  the values positionally. The migration runner's own bookkeeping is
  rendered by sea-query's `PostgresQueryBuilder`.
- `apply_harness_migrations(&Harness)` applies every module's migrations
  in lock order (module config order, then zero-padded migration id —
  the order `fz migrations collect` pins). Per module it applies the
  `postgres` migration set when the module ships one, else the `sqlite`
  set when it passes the portable-SQL lint (`cratefield-core`'s
  `lint_portable_sql`, the same predicate `fz doctor` enforces).
- `apply_migrations(module, &[SqlMigration])` is the per-module entry
  point: idempotent, each migration applied in its own transaction and
  tracked under `<module>/<id>` in `harness_migrations(id, applied_at)`.
- CLI: `fz migrations apply --dialect postgres --url …` (the `fz` binary
  must be built with cratefield-cli's `postgres` feature).

## The SQLite → Postgres mapping actually used

The portable subset is the intersection of both engines, so the same
migration files run verbatim:

| Portable concept | SQLite | Postgres | Notes |
|---|---|---|---|
| ULID ids | `TEXT PRIMARY KEY` | `TEXT PRIMARY KEY` | no `AUTOINCREMENT`/`SERIAL` |
| timestamps | ISO-8601 `TEXT` | ISO-8601 `TEXT` | computed in Rust, never `NOW()`/`datetime()` |
| counters | `INTEGER` | `INTEGER` (`int4`) | |
| booleans | `INTEGER 0/1` convention | `INTEGER 0/1` convention | no SQL `BOOLEAN` columns; `SeaValue::Bool` binds as `SMALLINT` 0/1 |
| upserts | `INSERT … ON CONFLICT …` | identical syntax | sea-query renders both |
| read-back writes | `INSERT … RETURNING …` | identical syntax | sea-query renders both |

## Tests

Tests that need a server are gated on `FZ_TEST_POSTGRES_URL` and skipped
with a printed reason when it is unset (CI provides a `postgres:16`
service container). Locally:

```sh
docker run --rm -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:16
export FZ_TEST_POSTGRES_URL=postgres://postgres:postgres@127.0.0.1:5433/postgres
cargo test -p cratefield-adapter-postgres
```

---

MIT. Built in the open for [Cratefield](https://cratefield.com), a [Factory Zero](https://factory0.ventures) venture.