Prevent duplicate active document versions

Replace the draft-only unique index with one covering both DRAFT and
PENDING_APPROVAL statuses, ensuring a document can have at most one
active version at a time. Deduplicate any existing data before creating
the index, prioritizing PENDING_APPROVAL over DRAFT.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-14 22:03:21 +02:00
parent 81de0e7b67
commit 0b9686fc43
2 changed files with 39 additions and 1 deletions

View File

@@ -253,7 +253,7 @@ VALUES (
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
if pgErr.Code == "23505" {
if pgErr.ConstraintName == "document_versions_document_id_major_minor_key" || pgErr.ConstraintName == "document_one_draft_version_idx" {
if pgErr.ConstraintName == "document_versions_document_id_major_minor_key" || pgErr.ConstraintName == "document_one_active_version_idx" {
return ErrResourceAlreadyExists
}
}

View File

@@ -0,0 +1,38 @@
-- Copyright (c) 2026 Probo Inc <hello@probo.inc>
--
-- Permission to use, copy, modify, and distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- The index constraint is already respected in Probo's production environment.
-- The deletion is here in case duplicates were created directly via SQL in
-- other contexts.
DELETE FROM document_versions
WHERE status IN ('DRAFT', 'PENDING_APPROVAL')
AND document_id IN (
SELECT document_id
FROM document_versions
WHERE status IN ('DRAFT', 'PENDING_APPROVAL')
GROUP BY document_id
HAVING COUNT(*) > 1
)
AND id NOT IN (
SELECT DISTINCT ON (document_id) id
FROM document_versions
WHERE status IN ('DRAFT', 'PENDING_APPROVAL')
ORDER BY document_id, CASE WHEN status = 'PENDING_APPROVAL' THEN 0 ELSE 1 END, created_at ASC
);
DROP INDEX document_one_draft_version_idx;
CREATE UNIQUE INDEX document_one_active_version_idx
ON document_versions (document_id)
WHERE status IN ('DRAFT', 'PENDING_APPROVAL');