refactor(kanban): use a native SQL upsert for task buckets

Replace the Exist-then-Update/Insert pair with a single dialect-aware upsert
(ON CONFLICT for postgres/sqlite, ON DUPLICATE KEY UPDATE for mysql/mariadb).
All four supported databases handle this natively, so it does the move in one
atomic statement and no longer depends on the affected-row count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JK7JwuvjhcsNf5d5fUopeY
This commit is contained in:
Claude 2026-06-18 21:36:34 +00:00
parent ba566ee9bc
commit 693fbb52b5
No known key found for this signature in database
1 changed files with 12 additions and 27 deletions

View File

@ -22,7 +22,9 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/web"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
// TaskBucket represents the relation between a task and a kanban bucket.
@ -58,35 +60,18 @@ func (b *TaskBucket) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
}
func (b *TaskBucket) upsert(s *xorm.Session) (err error) {
// Decide update vs. insert based on whether the row exists, not on the
// number of rows the UPDATE affects: MySQL/MariaDB report 0 affected rows
// when an UPDATE sets a column to the value it already holds, which would
// make us fall through to an INSERT and hit the unique index.
exists, err := s.Where("task_id = ? AND project_view_id = ?", b.TaskID, b.ProjectViewID).
Exist(&TaskBucket{})
if err != nil {
return
}
if exists {
_, err = s.Where("task_id = ? AND project_view_id = ?", b.TaskID, b.ProjectViewID).
Cols("bucket_id").
Update(b)
return
}
_, err = s.Insert(b)
if err != nil {
// Check if this is a unique constraint violation for the task_buckets table
if db.IsUniqueConstraintError(err, "UQE_task_buckets_task_project_view") {
return ErrTaskAlreadyExistsInBucket{
TaskID: b.TaskID,
ProjectViewID: b.ProjectViewID,
}
}
return
// A single native upsert keyed on the unique(task_id, project_view_id)
// index: it moves the task to the new bucket if the row exists and inserts
// it otherwise, atomically and without depending on the affected-row count
// (MySQL/MariaDB report 0 affected rows for an unchanged value).
query := "INSERT INTO task_buckets (task_id, project_view_id, bucket_id) VALUES (?, ?, ?) " +
"ON CONFLICT (task_id, project_view_id) DO UPDATE SET bucket_id = excluded.bucket_id"
if db.Type() == schemas.MYSQL {
query = "INSERT INTO task_buckets (task_id, project_view_id, bucket_id) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE bucket_id = VALUES(bucket_id)"
}
_, err = s.Exec(query, b.TaskID, b.ProjectViewID, b.BucketID)
return
}