fix(sqlite): update child_counters when explicit child IDs are created (GH#728)

When creating issues with explicit hierarchical IDs (e.g., bd-test.1, bd-test.2
via --id flag or import), the child_counters table was not being updated.
This caused GetNextChildID to return colliding IDs when later called with
--parent.

Changes:
- Add ensureChildCounterUpdatedWithConn() to update counter on explicit child creation
- Add ParseHierarchicalID() to extract parent and child number from IDs
- Update CreateIssue to call counter update after hierarchical ID validation
- Update EnsureIDs to call counter update when parent exists
- Add post-insert phase in batch operations to update counters after FK
  constraint can be satisfied
- Update tests to reflect new behavior where counter is properly initialized

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-24 13:22:55 -08:00
parent ce2b05356a
commit 1e7c7f5e54
6 changed files with 204 additions and 18 deletions

View File

@@ -102,6 +102,30 @@ func bulkMarkDirty(ctx context.Context, conn *sql.Conn, issues []*types.Issue) e
return markDirtyBatch(ctx, conn, issues)
}
// updateChildCountersForHierarchicalIDs updates child_counters for all hierarchical IDs in the batch.
// This is called AFTER issues are inserted so that parents exist for the foreign key constraint.
// (GH#728 fix)
func updateChildCountersForHierarchicalIDs(ctx context.Context, conn *sql.Conn, issues []*types.Issue) error {
for _, issue := range issues {
if issue.ID == "" {
continue // Skip issues that were filtered out (e.g., OrphanSkip)
}
if parentID, childNum, ok := ParseHierarchicalID(issue.ID); ok {
// Only update if parent exists (it should after insert, but check to be safe)
var parentCount int
if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM issues WHERE id = ?`, parentID).Scan(&parentCount); err != nil {
return fmt.Errorf("failed to check parent existence for %s: %w", parentID, err)
}
if parentCount > 0 {
if err := ensureChildCounterUpdatedWithConn(ctx, conn, parentID, childNum); err != nil {
return err
}
}
}
}
return nil
}
// checkForExistingIDs verifies that:
// 1. There are no duplicate IDs within the batch itself
// 2. None of the issue IDs already exist in the database
@@ -271,6 +295,12 @@ func (s *SQLiteStorage) CreateIssuesWithFullOptions(ctx context.Context, issues
return wrapDBError("bulk insert issues", err)
}
// Phase 4.5: Update child counters for hierarchical IDs (GH#728 fix)
// This must happen AFTER insert so parents exist for the foreign key constraint
if err := updateChildCountersForHierarchicalIDs(ctx, conn, issues); err != nil {
return wrapDBError("update child counters", err)
}
// Phase 5: Record creation events
if err := bulkRecordEvents(ctx, conn, issues, actor); err != nil {
return wrapDBError("record creation events", err)