Files
query-orchestration/internal/database/queries/custommetadata.sql
T
Jay Brown 17fc813823 Merged in feature/mutable-metadata1 (pull request #221)
M1, M2 and M3 complete

* M1, M2 and M3 complete

* review changes

* docs

* docs
2026-04-16 23:11:26 +00:00

187 lines
7.5 KiB
SQL

-- Mutable metadata feature, milestone 2.3: document_custom_metadata CRUD
-- queries plus document custom_schema_id accessors.
--
-- See plan Section 8.2 / tracking §2.3 of
-- plans/mutable.metadata.plan.combo.v4.md for the canonical list.
--
-- v4 notes:
-- - No schema_id parameter on the metadata insert. The schema binding
-- lives on the document row; metadata rows don't duplicate it.
-- - GetCurrentDocumentCustomMetadata and GetDocumentCustomMetadataByVersion
-- join documents + client_metadata_schemas so the handler can populate
-- schemaId/schemaName/schemaVersion in one round-trip.
-- - LockDocumentForMetadataWrite takes a parent-row lock on documents.
-- Replaces v3's LockDocumentCustomMetadataForVersion.
-- - DeleteDocumentCustomMetadata / NullifyDocumentCustomSchemaId are
-- intentionally absent: FK cascades handle document deletion and the
-- reset-metadata workstream writes a direct UPDATE instead.
-- name: CreateDocumentCustomMetadata :one
-- Insert a new metadata version for a document. Caller supplies an
-- explicit version computed from GetMaxDocumentMetadataVersion + 1 under
-- the parent-row lock from LockDocumentForMetadataWrite. No schema_id
-- column exists on this table in v4.
INSERT INTO document_custom_metadata (
document_id,
metadata,
version,
created_by
) VALUES (
@document_id,
@metadata,
@version,
@created_by
)
RETURNING *;
-- name: GetCurrentDocumentCustomMetadata :one
-- Return the most-recent metadata row for a document, decorated with the
-- current schema name and version pulled from the bound schema row.
-- Joins documents (for the binding) and client_metadata_schemas (for
-- name/version). idx_dcm_doc_version_desc backs the ORDER BY ... LIMIT 1.
SELECT
dcm.id,
dcm.document_id,
dcm.metadata,
dcm.version,
dcm.created_at,
dcm.created_by,
cms.id AS schema_id,
cms.name AS schema_name,
cms.version AS schema_version
FROM document_custom_metadata dcm
JOIN documents d ON d.id = dcm.document_id
LEFT JOIN client_metadata_schemas cms ON cms.id = d.custom_schema_id
WHERE dcm.document_id = @document_id
ORDER BY dcm.version DESC
LIMIT 1;
-- name: GetDocumentCustomMetadataByVersion :one
-- Return a specific historical version with the same schema decoration
-- as the current-version query above.
SELECT
dcm.id,
dcm.document_id,
dcm.metadata,
dcm.version,
dcm.created_at,
dcm.created_by,
cms.id AS schema_id,
cms.name AS schema_name,
cms.version AS schema_version
FROM document_custom_metadata dcm
JOIN documents d ON d.id = dcm.document_id
LEFT JOIN client_metadata_schemas cms ON cms.id = d.custom_schema_id
WHERE dcm.document_id = @document_id
AND dcm.version = @version;
-- name: GetDocumentCustomMetadataHistory :many
-- Return a paged list of version summaries for a document, newest first.
-- No schema join: the history response is a terse list of
-- (version, created_at, created_by) tuples.
SELECT
version,
created_at,
created_by
FROM document_custom_metadata
WHERE document_id = @document_id
ORDER BY version DESC
LIMIT @limit_val OFFSET @offset_val;
-- name: LockDocumentForMetadataWrite :one
-- Parent-row lock for metadata write ordering. Must be called inside a
-- transaction as the FIRST DML statement. Serializes with AssignSchema
-- (which also takes FOR UPDATE on documents) so the mutual-exclusivity
-- invariant in plan Section 7.3 holds.
SELECT id FROM documents
WHERE id = @document_id
FOR UPDATE;
-- name: GetMaxDocumentMetadataVersion :one
-- Return MAX(version) for a document's metadata rows, or 0 when none
-- exist. Caller adds 1 to compute the next version to insert. Must run
-- inside the transaction that holds the LockDocumentForMetadataWrite
-- lock; otherwise two concurrent writers can both see the same max.
SELECT COALESCE(MAX(version), 0)::int AS max_version
FROM document_custom_metadata
WHERE document_id = @document_id;
-- name: SetDocumentCustomSchemaId :exec
-- Bind (or clear) a document's schema. Nullable parameter: passing NULL
-- clears the binding. The composite FK in migration 128 prevents binding
-- a schema owned by a different client, so this query does not re-check.
UPDATE documents
SET custom_schema_id = @custom_schema_id
WHERE id = @id;
-- name: GetDocumentCustomSchemaId :one
-- Read a document's current schema binding. Returns NULL when unbound.
SELECT custom_schema_id
FROM documents
WHERE id = @id;
-- name: GetDocumentClientID :one
-- Read a document's clientId for the AssignSchema cross-client check.
-- documents.clientId is the unquoted (lowercased) varchar column.
SELECT clientId FROM documents WHERE id = @id;
-- name: HasDocumentCustomMetadata :one
-- EXISTS probe for any metadata row belonging to this document. Used by
-- AssignSchema to reject re-binding once metadata has been written.
SELECT EXISTS(
SELECT 1 FROM document_custom_metadata WHERE document_id = @document_id
) AS has_metadata;
-- name: LockDocumentForReset :one
-- Milestone 3 parent-row lock acquired by ResetDocumentMetadata as the
-- FIRST DML statement inside its transaction. Same FOR UPDATE shape as
-- LockDocumentForMetadataWrite / LockDocumentForLegacyExtractionWrite so
-- the reset path serializes correctly against concurrent assign and write
-- paths for the mutual-exclusivity invariant (plan Section 7.3).
-- Returns id, clientid, and current custom_schema_id so the caller can
-- short-circuit and/or decorate the result without a second round-trip.
SELECT id, clientId, custom_schema_id
FROM documents
WHERE id = @document_id
FOR UPDATE;
-- name: GetDocumentSchemaBindingForReset :one
-- Read the document's current schema binding along with the joined
-- schema row's name and version so ResetDocumentMetadata can populate the
-- previousSchema* fields on its response. The LEFT JOIN returns NULL for
-- name/version when the document has no schema bound — the reset endpoint
-- treats that as the idempotent no-op case and still succeeds.
SELECT
d.custom_schema_id,
cms.name AS schema_name,
cms.version AS schema_version
FROM documents d
LEFT JOIN client_metadata_schemas cms ON cms.id = d.custom_schema_id
WHERE d.id = @document_id;
-- name: CountDocumentCustomMetadataVersions :one
-- Return the number of metadata version rows for a document. Used by
-- ResetDocumentMetadata to populate metadataVersionsDeleted on its
-- response. Run under the parent-row lock from LockDocumentForReset so
-- the count is authoritative at commit time.
SELECT COUNT(*)::int AS version_count
FROM document_custom_metadata
WHERE document_id = @document_id;
-- name: DeleteDocumentCustomMetadataForReset :exec
-- @sqlc-vet-disable
-- Delete every metadata version row for a document. Owned by the
-- reset-metadata path — the Delete cascade for document deletion is
-- handled by ON DELETE CASCADE on document_custom_metadata.document_id,
-- not by this query. Name disambiguates from any future cascade helper.
DELETE FROM document_custom_metadata WHERE document_id = @document_id;
-- name: NullifyDocumentCustomSchemaIdForReset :exec
-- Clear the document's schema binding as the final DML of the reset
-- transaction. Trigger 1 (trg_prevent_schema_reassignment) fires on this
-- UPDATE; the preceding DeleteDocumentCustomMetadataForReset has already
-- removed every metadata row and the transaction has already rejected any
-- document carrying legacy field extractions, so the trigger's EXISTS
-- checks both return false and the UPDATE succeeds.
UPDATE documents SET custom_schema_id = NULL WHERE id = @document_id;