feat(datastore): container_tags store (S12 Step 4)

Why

Implements S12 Step 4 per the OCI Local plan. Provides ContainerTagStore CRUD and case-insensitive keyset pagination for S12 AC-18. Foundation for S12 Steps 12 (manifest-push tag upsert), 14 (manifest-delete tag cascade), and 15 (GET tags/list handler).

What

hasMore from ListByImage reports cursor-predicate match even when limit=0. The handler's n=0 Link-header policy is fixed by S12 (no Link); hasMore exists for handler visibility, not Link emission.

Context for LLM agents

Rationale

  • Keyset predicate LOWER(name) > LOWER(?) OR (LOWER(name) = LOWER(?) AND name > ?). Rejected LOWER(name) >= LOWER(?) AND name > ? because the latter requires a sort after the seek; the chosen shape lets the planner pick the (namespace_id, container_image_id, LOWER(name)) index. EXPLAIN-asserted at 50 seed rows with enable_seqscan=off on a dedicated *sql.Conn, rather than seeding tens of thousands of rows to coax the planner naturally.
  • Create, Delete, and FindByName accept caller qrm.DB (transaction-capable). ListByImage reads through s.client.DB() because the Step 15 GET handler does not run in a tx. FindByName was extended in this round per jdrpereira's review. Earlier it only accepted s.client.DB().

Non-goals

  • Bulk DeleteByManifest for the Step 14 cascade. S12 Step 14 will add it with RowsAffected.
  • Create return type carrying CreatedAt/UpdatedAt. Callers needing them use FindByName.
  • Named-query metric emission. TODO markers reference #54. Emission lands when S03's helper does.
Edited by Hayley Swimelar

Merge request reports

Loading
Loading