feat: Add composite index for dependency queries

- Add idx_dependencies_depends_on_type index on (depends_on_id, type)
- Optimize queries filtering by both target issue and dependency type
- Improve performance for dep tree and relationship queries
- Update plugin version to 0.9.5
- Sync issue database

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-10-15 00:20:31 -07:00
parent 2cc17a9acf
commit b8f0b5e71f
3 changed files with 261 additions and 221 deletions

File diff suppressed because one or more lines are too long

View File

@@ -26,7 +26,8 @@
"beads-mcp"
],
"env": {
"BEADS_ACTOR": "${USER}"
"BEADS_ACTOR": "${USER}",
"BEADS_WORKING_DIR": "${PWD}"
}
}
}

View File

@@ -29,8 +29,9 @@ func New(path string) (*SQLiteStorage, error) {
return nil, fmt.Errorf("failed to create directory: %w", err)
}
// Open database with WAL mode for better concurrency
db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_foreign_keys=ON")
// Open database with WAL mode for better concurrency and busy timeout for parallel writes
// _pragma=busy_timeout(10000) means wait up to 10 seconds for locks instead of failing immediately
db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_foreign_keys=ON&_pragma=busy_timeout(10000)")
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
@@ -317,41 +318,67 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
return fmt.Errorf("validation failed: %w", err)
}
// Generate ID if not set (using atomic counter table)
if issue.ID == "" {
// Get prefix from config, default to "bd"
prefix, err := s.GetConfig(ctx, "issue_prefix")
if err != nil || prefix == "" {
prefix = "bd"
}
// Ensure counter is initialized for this prefix (lazy initialization)
// Only scans issues with this prefix on first use, not the entire table
if err := s.ensureCounterInitialized(ctx, prefix); err != nil {
return fmt.Errorf("failed to initialize counter: %w", err)
}
// Atomically get next ID from counter table
nextID, err := s.getNextIDForPrefix(ctx, prefix)
if err != nil {
return err
}
issue.ID = fmt.Sprintf("%s-%d", prefix, nextID)
}
// Set timestamps
now := time.Now()
issue.CreatedAt = now
issue.UpdatedAt = now
// Start transaction
// Start transaction BEFORE ID generation to prevent race conditions
// This ensures ID generation and issue insertion are atomic
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer tx.Rollback()
// Generate ID if not set (inside transaction to prevent race conditions)
if issue.ID == "" {
// Get prefix from config, default to "bd"
var prefix string
err := tx.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
if err == sql.ErrNoRows || prefix == "" {
prefix = "bd"
} else if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
// Ensure counter is initialized for this prefix (lazy initialization within transaction)
var exists int
err = tx.QueryRowContext(ctx, `SELECT 1 FROM issue_counters WHERE prefix = ?`, prefix).Scan(&exists)
if err == sql.ErrNoRows {
// Counter doesn't exist, initialize it from existing issues
_, err = tx.ExecContext(ctx, `
INSERT INTO issue_counters (prefix, last_id)
SELECT ?, COALESCE(MAX(CAST(substr(id, LENGTH(?) + 2) AS INTEGER)), 0)
FROM issues
WHERE id LIKE ? || '-%'
AND substr(id, LENGTH(?) + 2) GLOB '[0-9]*'
ON CONFLICT(prefix) DO UPDATE SET
last_id = MAX(last_id, excluded.last_id)
`, prefix, prefix, prefix, prefix)
if err != nil {
return fmt.Errorf("failed to initialize counter for prefix %s: %w", prefix, err)
}
} else if err != nil {
return fmt.Errorf("failed to check counter existence: %w", err)
}
// Atomically get next ID from counter table (within transaction)
var nextID int
err = tx.QueryRowContext(ctx, `
INSERT INTO issue_counters (prefix, last_id)
VALUES (?, 1)
ON CONFLICT(prefix) DO UPDATE SET
last_id = last_id + 1
RETURNING last_id
`, prefix).Scan(&nextID)
if err != nil {
return fmt.Errorf("failed to generate next ID for prefix %s: %w", prefix, err)
}
issue.ID = fmt.Sprintf("%s-%d", prefix, nextID)
}
// Insert issue
_, err = tx.ExecContext(ctx, `
INSERT INTO issues (