-- Mutable metadata feature, milestone 1.4: schema CRUD queries for -- client_metadata_schemas. See plan Section 8.2 of -- plans/mutable.metadata.plan.combo.v4.md for the canonical list. -- -- Intentionally omitted in v4: -- - GetSchemaMetadataRecordCount (removed; canDelete derives from -- GetSchemaDocumentCount alone because document_custom_metadata has -- no schema_id column in v4). -- - Metadata, document-modification, and reset-metadata queries -- (Milestones 2 and 3, plan sections 8.2 continued). -- name: CreateClientMetadataSchema :one -- Insert a new schema row. Caller supplies an explicit version computed -- from GetMaxSchemaVersion + 1 and an explicit status (always 'active' -- in Milestone 1; supersede/retire go through SetSchemaStatus). The -- database defaults id and created_at. INSERT INTO client_metadata_schemas ( client_id, name, description, schema_def, version, status, created_by ) VALUES ( @client_id, @name, @description, @schema_def, @version, @status, @created_by ) RETURNING *; -- name: GetClientMetadataSchema :one -- Fetch a single schema by primary key. SELECT * FROM client_metadata_schemas WHERE id = $1; -- name: ListClientMetadataSchemas :many -- List schemas for a client with optional filters. Status filtering uses a -- two-parameter shape: when include_all_statuses is true, status is ignored; -- otherwise status_filter selects exactly one of active/superseded/retired. -- When include_all_versions is false, only the latest version per -- (client_id, name) is returned (via a max-version subquery). When true, -- every version is returned. Name filter uses sqlc.narg so passing NULL -- skips the filter; a non-NULL value is matched exactly. The document_count -- column is a correlated subquery so the handler can derive canDelete -- without a second round-trip. SELECT cms.*, (SELECT COUNT(*) FROM documents WHERE documents.custom_schema_id = cms.id)::bigint AS document_count FROM client_metadata_schemas cms WHERE cms.client_id = @client_id AND (sqlc.narg(name_filter)::varchar IS NULL OR cms.name = sqlc.narg(name_filter)::varchar) AND (@include_all_statuses::bool OR cms.status = @status_filter::schema_status_type) AND ( @include_all_versions::bool OR (cms.client_id, cms.name, cms.version) IN ( SELECT client_id, name, MAX(version) FROM client_metadata_schemas WHERE client_id = @client_id GROUP BY client_id, name ) ) ORDER BY cms.name, cms.version DESC LIMIT @limit_val OFFSET @offset_val; -- name: GetLatestSchemaByName :one -- Return the highest-version row for a (client_id, name) pair regardless -- of status. Callers that need an active-only view should filter after. SELECT * FROM client_metadata_schemas WHERE client_id = @client_id AND name = @name ORDER BY version DESC LIMIT 1; -- name: GetSchemaDocumentCount :one -- Count documents currently bound to a schema via documents.custom_schema_id. -- Used to derive canDelete and to drive retire-vs-delete decisions. SELECT COUNT(*) AS document_count FROM documents WHERE custom_schema_id = @schema_id; -- name: SetSchemaStatus :exec -- Update a schema's status. Used by the service layer to mark a row -- superseded when a new version is created, and to mark a row retired -- when the schema still has documents bound at delete time. UPDATE client_metadata_schemas SET status = @status WHERE id = @id; -- name: GetMaxSchemaVersion :one -- Get the current max version for a (client_id, name) pair. Returns 0 -- for a brand-new pair so the service layer can safely write version 1. -- Must be called inside the same transaction that holds the lock from -- LockSchemaVersionsForName. SELECT COALESCE(MAX(version), 0) AS max_version FROM client_metadata_schemas WHERE client_id = @client_id AND name = @name; -- name: LockSchemaVersionsForName :many -- Lock every version row of a (client_id, name) pair with FOR UPDATE to -- serialize concurrent version creation. Returns the locked rows so the -- service can read the current active row id and max version in one -- statement. Must be called inside a transaction. SELECT * FROM client_metadata_schemas WHERE client_id = @client_id AND name = @name FOR UPDATE;