-- Entity read + mutation queries (API paths). Aliased `e` throughout to -- avoid ambiguity with joined tables. -- name: GetEntityByID :one SELECT e.* FROM entities e WHERE e.id = $1; -- name: GetEntityBySlug :one SELECT e.* FROM entities e WHERE e.slug = $1; -- name: ListEntities :many WITH RECURSIVE tt AS ( SELECT name FROM entity_types WHERE sqlc.narg('type')::text IS NULL OR name = sqlc.narg('type') UNION SELECT et.name FROM entity_types et JOIN tt ON et.parent_type = tt.name WHERE sqlc.narg('type')::text IS NOT NULL ) SELECT e.* FROM entities e JOIN entity_types et ON et.name = e.type WHERE e.type IN (SELECT name FROM tt) AND (sqlc.narg('state')::text IS NULL OR e.state = sqlc.narg('state')) AND (sqlc.narg('domain')::text IS NULL OR et.domain = sqlc.narg('domain')) AND (sqlc.narg('layer')::text IS NULL OR et.layer = sqlc.narg('layer')) AND (sqlc.narg('q')::text IS NULL OR e.slug ILIKE '%'||sqlc.narg('q')||'%' OR e.name ILIKE '%'||sqlc.narg('q')||'%') AND (sqlc.narg('cursor')::text IS NULL OR e.slug > sqlc.narg('cursor')) ORDER BY e.slug LIMIT sqlc.arg('lim'); -- name: InsertEntity :one INSERT INTO entities (id, slug, type, name, state, attributes) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; -- name: UpdateEntity :one UPDATE entities SET name = COALESCE(sqlc.narg('name'), name), state = COALESCE(sqlc.narg('state'), state), attributes = COALESCE(sqlc.narg('attributes'), attributes), maintenance_until = CASE WHEN sqlc.arg('set_maintenance')::bool THEN sqlc.narg('maintenance_until') ELSE maintenance_until END, version = version + 1, updated_at = now() WHERE id = sqlc.arg('id') AND version = sqlc.arg('version') RETURNING *; -- name: MergeEntityAttributes :execrows -- Shallow-merge a JSON patch into an entity's attributes (the -- update_entity_attributes MCP/HTTP surface). Replaces the raw -- `attributes = attributes || $2::jsonb` used in entity_tools.go. UPDATE entities SET attributes = attributes || sqlc.arg('patch')::jsonb, updated_at = now() WHERE slug = sqlc.arg('slug'); -- name: SetEntityState :execrows -- Set an entity's lifecycle state by id (the set_entity_state surface, run -- after db.ValidateTransition). Replaces the raw -- `UPDATE entities SET state = $2 ... WHERE id = $1`. UPDATE entities SET state = sqlc.arg('state'), updated_at = now() WHERE id = sqlc.arg('id'); -- blast_radius(): the recursive-CTE traversal function's TABLE return type -- is opaque to sqlc's analyzer — that one query stays hand-written pgx in -- internal/httpapi (see entities.go GetBlastRadius). -- -- Deliberate raw-SQL exceptions (plan E2): the httpapi entity *read* handlers -- (ListEntities/GetEntity/GetGraph/queryEntities) project a fixed -- `entityCols` column set (entities.* + a LEFT JOIN to entity_status for -- health/last_check_at) and scan it positionally into the oapi-generated -- gen.Entity shape. sqlc generates its own row struct per query and cannot -- emit gen.Entity, so migrating those reads would add a per-call field-by- -- field mapping with no compile-time gain and real column-order risk. They -- stay hand-written pgx, like blast_radius and the seed/export bulk paths -- noted in sqlc.yaml. The mutation/relationship surface (MergeEntityAttributes, -- SetEntityState, InsertRelationshipIfAbsent, EndCurrentRelationship) IS -- migrated and is what the entity CRUD tools now call.