fix: replace in-memory ID counter with atomic database counter
Replace the in-memory nextID counter with an atomic database-backed counter using the issue_counters table. This fixes race conditions when multiple processes create issues concurrently. Changes: - Add issue_counters table with atomic INSERT...ON CONFLICT pattern - Remove in-memory nextID field and sync.Mutex from SQLiteStorage - Implement getNextIDForPrefix() for atomic ID generation - Update CreateIssue() to use database counter instead of memory - Update RemapCollisions() to use database counter for collision resolution - Clean up old planning and bug documentation files Fixes the multi-process ID generation race condition tested in cmd/bd/race_test.go.
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
# BUG FOUND: getNextID() uses alphabetical MAX instead of numerical
|
||||
|
||||
## Location
|
||||
`internal/storage/sqlite/sqlite.go:60-84`, function `getNextID()`
|
||||
|
||||
## The Bug
|
||||
```go
|
||||
err := db.QueryRow("SELECT MAX(id) FROM issues").Scan(&maxID)
|
||||
```
|
||||
|
||||
This uses alphabetical MAX on the text `id` column, not numerical MAX.
|
||||
|
||||
## Impact
|
||||
When you have bd-1 through bd-10:
|
||||
- Alphabetical sort: bd-1, bd-10, bd-2, bd-3, ... bd-9
|
||||
- MAX(id) returns "bd-9" (alphabetically last)
|
||||
- nextID is calculated as 10
|
||||
- Creating a new issue tries to use bd-10, which already exists
|
||||
- Result: UNIQUE constraint failed
|
||||
|
||||
## Reproduction
|
||||
```bash
|
||||
# After creating bd-1 through bd-10
|
||||
./bd create "Test issue" -t task -p 1
|
||||
# Error: failed to insert issue: UNIQUE constraint failed: issues.id
|
||||
```
|
||||
|
||||
## The Fix
|
||||
|
||||
Option 1: Cast to integer in SQL (BEST)
|
||||
```sql
|
||||
SELECT MAX(CAST(SUBSTR(id, INSTR(id, '-') + 1) AS INTEGER)) FROM issues WHERE id LIKE 'bd-%'
|
||||
```
|
||||
|
||||
Option 2: Pad IDs with zeros
|
||||
- Change ID format from "bd-10" to "bd-0010"
|
||||
- Alphabetical and numerical order match
|
||||
- Breaks existing IDs
|
||||
|
||||
Option 3: Query all IDs and find max in Go
|
||||
- Less efficient but more flexible
|
||||
- Works with any ID format
|
||||
|
||||
## Recommended Solution
|
||||
|
||||
Option 1 with proper prefix handling:
|
||||
|
||||
```go
|
||||
func getNextID(db *sql.DB) int {
|
||||
// Get prefix from config (default "bd")
|
||||
var prefix string
|
||||
err := db.QueryRow("SELECT value FROM config WHERE key = 'issue_prefix'").Scan(&prefix)
|
||||
if err != nil || prefix == "" {
|
||||
prefix = "bd"
|
||||
}
|
||||
|
||||
// Find max numeric ID for this prefix
|
||||
var maxNum sql.NullInt64
|
||||
query := `
|
||||
SELECT MAX(CAST(SUBSTR(id, LENGTH(?) + 2) AS INTEGER))
|
||||
FROM issues
|
||||
WHERE id LIKE ? || '-%'
|
||||
`
|
||||
err = db.QueryRow(query, prefix, prefix).Scan(&maxNum)
|
||||
if err != nil || !maxNum.Valid {
|
||||
return 1
|
||||
}
|
||||
|
||||
return int(maxNum.Int64) + 1
|
||||
}
|
||||
```
|
||||
|
||||
## Workaround for Now
|
||||
|
||||
Manually specify IDs when creating issues:
|
||||
```bash
|
||||
# This won't work because auto-ID fails:
|
||||
./bd create "Title" -t task -p 1
|
||||
|
||||
# Workaround - manually calculate next ID:
|
||||
./bd list | grep -oE 'bd-[0-9]+' | sed 's/bd-//' | sort -n | tail -1
|
||||
# Then add 1 and create with explicit ID in code
|
||||
```
|
||||
|
||||
Or fix the bug first before continuing!
|
||||
|
||||
## Related to bd-9
|
||||
|
||||
This bug is EXACTLY the kind of distributed ID collision problem that bd-9 is designed to solve! But we should also fix the root cause.
|
||||
|
||||
## Created Issue
|
||||
|
||||
Should create: "Fix getNextID() to use numerical MAX instead of alphabetical"
|
||||
- Type: bug
|
||||
- Priority: 0 (critical - blocks all new issue creation)
|
||||
- Blocks: bd-9 (can't create child issues)
|
||||
Reference in New Issue
Block a user