feat(datastore): container manifest persistence for manifest push (S12 Step 12, 1/3)

Why

First of three MRs splitting the OCI manifest push endpoint (S12 Step 12) into reviewable layers, at a reviewer's request on the original combined MR, feat(oci): manifest push endpoint (S12 Step 12) (!511 - closed) • Hayley Swimelar. This slice is the datastore layer, reviewable on its own for SQL correctness and the persist transaction, with no format-layer or HTTP concern.

Review the stack in order: this MR, then the manifest logic in feat(oci): manifest push parsing, validation, a... (!519 - merged) • Hayley Swimelar, then the wiring and e2e in feat(oci): wire manifest push endpoint with dat... (!520 - merged) • Hayley Swimelar.

What (non-obvious)

  • The 6b persist transaction lives in datastore.ContainerManifestPersister, mirroring Step 9's ContainerBlobLinker, so raw SQL stays in the datastore layer per ADR-023 and the format package never imports database/sql.
  • Child-manifest existence is one batched WHERE digest = ANY(...) query per push regardless of child count (never per-descriptor), and the same query feeds the Image Index tree-size computation.
  • Image Index relationships insert in one batched statement (ContainerManifestRelationshipStore.CreateBatch), so an index push holds the tag FOR UPDATE lock for one round-trip rather than one per child.
  • No migration: the container_* and blob_storage_* schema already exists from the S06 foundation.

Reviewer notes

  • Size: this slice runs to ~1,860 reviewable LOC, over the 500 ceiling. It is the datastore third of the reviewer-requested Step 12 split (the plan estimated Step 12 at ~850 LOC and flagged it over 500), and roughly half the diff is integration tests, including the ContainerManifestPersister transaction tests. Splitting further would separate the persist transaction from its tests.
  • Query plans: the new transactional queries (resolveChildManifestIDs and the manifest/tag counts) are served by the existing unique and partition-key indexes. Capturing EXPLAIN (ANALYZE, BUFFERS) against a cap-sized image (25,000 manifests, 1,000 tags) is a follow-up; it is not meaningful on a small dataset.

Test plan

go test -tags=integration ./internal/datastore/... (the persister and reference-existence integration tests) runs in CI's test:integration job. go build ./..., go vet (default and integration tags), and golangci-lint 2.12 pass locally.

Related to #19 (closed)

Edited by Hayley Swimelar

Merge request reports

Loading
Loading