Database providers
A database provider is what creates the copy of production each environment gets. It is the main extension point, and it is meant to be written by people outside this repository.
database: provider: docker # or neon version: 17What ships
Section titled “What ships”| Provider | Where the data lives | Branch time | Needs |
|---|---|---|---|
docker | A container on the machine running af | Grows with the database | A Docker daemon |
neon | A Neon project | Flat, because branches share storage | A Neon project and an API key |
docker is the default and needs nothing. It is the right choice for a
repository whose database is small enough that copying it is not the slow part.
neon is the right choice when it is. Neon branches are copy on write, so
creating one takes about as long for a hundred gigabytes as for a hundred rows.
A provider named in the manifest and not built into this binary is refused at
startup rather than substituted. Falling back to docker would hand somebody
an empty preview with no reason for it.
What every provider guarantees
Section titled “What every provider guarantees”These are not documentation. They are a conformance suite that any implementation runs, so that “conformant” is something a test decides rather than something a maintainer judges.
- A refresh masks, then verifies, and publishes nothing if verification fails.
- An unverified golden cannot be branched. This is the product’s central promise and it is enforced in the provider, not in a checklist.
- Branching twice for one environment returns one branch. The engine retries after timeouts, and a retry that creates a second resource is how an orphan is made.
- Destroying something already destroyed succeeds, because teardown retries.
- A connection string is a secret: it renders as
[redacted]everywhere text is produced. - Every resource the provider holds can be enumerated, so the leak detector has something to compare the journal against.
- A capability a provider does not have is skipped by name in the suite output, never silently.
Direct and pooled connections
Section titled “Direct and pooled connections”A provider may offer a pooled endpoint. Where it does, services receive the pooled connection string and migrations receive the direct one, because a transaction pooler does not support the session level features migrations use. Where it does not, both receive the same string.
Nothing has to be configured for this. The engine asks based on what the provider declares.
Writing one
Section titled “Writing one”Implement provider.Database and run the suite:
func TestMyProvider(t *testing.T) { conformance.RunDatabase(t, factory, conformance.Options{})}Declare only the capabilities you actually have. Declaring one you do not makes the suite run a behaviour it should have skipped, which fails, which is the intended outcome: a capability is a promise the suite checks.