72de690894
Chatbot functionality * baseline working * missing test file * more tests
220 lines
8.6 KiB
SQL
220 lines
8.6 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: GetDocumentCustomSchemaIdForClient :one
|
|
-- Chatbot-scoped variant of GetDocumentCustomSchemaId: adds a clientId
|
|
-- filter so cross-client lookups miss with pgx.ErrNoRows. Same scalar
|
|
-- result type (nullable uuid) as the non-scoped query — only the WHERE
|
|
-- clause differs. Plan §5.
|
|
SELECT custom_schema_id
|
|
FROM documents
|
|
WHERE id = @id
|
|
AND clientId = @client_id;
|
|
|
|
-- name: GetCurrentDocumentCustomMetadataForClient :one
|
|
-- Chatbot-scoped variant of GetCurrentDocumentCustomMetadata. Same column
|
|
-- set (the metadata row decorated with schema id/name/version), but the
|
|
-- joined documents row must belong to @client_id; cross-client calls miss
|
|
-- with pgx.ErrNoRows. Plan §5.
|
|
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 d.clientId = @client_id
|
|
ORDER BY dcm.version DESC
|
|
LIMIT 1;
|
|
|
|
-- 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;
|