Replace per-creator name uniqueness with a global index among non-retired projects, and reject cross-user duplicate creates with 409. Co-authored-by: Cursor <cursoragent@cursor.com>
39 lines
1.2 KiB
SQL
39 lines
1.2 KiB
SQL
-- +goose Up
|
|
-- 全局项目名唯一:任意活跃项目不得重名(跨创建人)。
|
|
-- 替换 00056 的 per-creator 唯一索引。
|
|
|
|
-- Deduplicate globally by lower(trim(name)) among non-retired rows (keep lowest id).
|
|
WITH dups AS (
|
|
SELECT id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY lower(trim(name))
|
|
ORDER BY id ASC
|
|
) AS rn
|
|
FROM projects
|
|
WHERE length(trim(name)) > 0
|
|
AND COALESCE(status, 'active') <> 'demo_retired'
|
|
)
|
|
UPDATE projects p
|
|
SET name = p.name || ' (dup-' || p.id || ')',
|
|
updated_at = now()
|
|
FROM dups d
|
|
WHERE p.id = d.id AND d.rn > 1;
|
|
|
|
DROP INDEX IF EXISTS uq_projects_creator_name;
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_name_global
|
|
ON projects (lower(trim(name)))
|
|
WHERE length(trim(name)) > 0
|
|
AND COALESCE(status, 'active') <> 'demo_retired';
|
|
|
|
COMMENT ON INDEX uq_projects_name_global IS
|
|
'Global unique project name among non-retired projects (case-insensitive, trimmed).';
|
|
|
|
-- +goose Down
|
|
DROP INDEX IF EXISTS uq_projects_name_global;
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_creator_name
|
|
ON projects (creator_user_id, lower(trim(name)))
|
|
WHERE creator_user_id IS NOT NULL AND creator_user_id > 0
|
|
AND length(trim(name)) > 0;
|