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

@@ -78,11 +78,11 @@ func TestGetNextChildNumber(t *testing.T) {
func TestGetNextChildNumber_DifferentParents(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
parent1 := "bd-af78e9a2"
parent2 := "bd-af78e9a2.1"
parent2 := "bd-bf89e0b3" // Use non-hierarchical ID to avoid counter interaction
// Create parent issues first
for _, id := range []string{parent1, parent2} {
parent := &types.Issue{
@@ -97,7 +97,7 @@ func TestGetNextChildNumber_DifferentParents(t *testing.T) {
t.Fatalf("failed to create parent issue %s: %v", id, err)
}
}
// Each parent should have independent counters
child1_1, err := store.getNextChildNumber(ctx, parent1)
if err != nil {
@@ -106,7 +106,7 @@ func TestGetNextChildNumber_DifferentParents(t *testing.T) {
if child1_1 != 1 {
t.Errorf("expected parent1 child to be 1, got %d", child1_1)
}
child2_1, err := store.getNextChildNumber(ctx, parent2)
if err != nil {
t.Fatalf("getNextChildNumber failed: %v", err)
@@ -114,7 +114,7 @@ func TestGetNextChildNumber_DifferentParents(t *testing.T) {
if child2_1 != 1 {
t.Errorf("expected parent2 child to be 1, got %d", child2_1)
}
child1_2, err := store.getNextChildNumber(ctx, parent1)
if err != nil {
t.Fatalf("getNextChildNumber failed: %v", err)
@@ -186,10 +186,12 @@ func TestGetNextChildNumber_Concurrent(t *testing.T) {
func TestGetNextChildNumber_NestedHierarchy(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create parent issues for nested hierarchy
// Note: When creating bd-af78e9a2.1, the counter for bd-af78e9a2 is set to 1 (GH#728 fix)
// When creating bd-af78e9a2.1.2, the counter for bd-af78e9a2.1 is set to 2 (GH#728 fix)
parents := []string{"bd-af78e9a2", "bd-af78e9a2.1", "bd-af78e9a2.1.2"}
for _, id := range parents {
parent := &types.Issue{
@@ -204,21 +206,21 @@ func TestGetNextChildNumber_NestedHierarchy(t *testing.T) {
t.Fatalf("failed to create parent issue %s: %v", id, err)
}
}
// Create nested hierarchy counters
// bd-af78e9a2 → .1, .2
// bd-af78e9a2.1 → .1.1, .1.2
// bd-af78e9a2.1.2 → .1.2.1, .1.2.2
// With GH#728 fix, counters are updated when explicit hierarchical IDs are created:
// - Creating bd-af78e9a2.1 sets counter for bd-af78e9a2 to 1
// - Creating bd-af78e9a2.1.2 sets counter for bd-af78e9a2.1 to 2
// So getNextChildNumber returns the NEXT number after the existing children
tests := []struct {
parent string
expected []int
}{
{"bd-af78e9a2", []int{1, 2}},
{"bd-af78e9a2.1", []int{1, 2}},
{"bd-af78e9a2.1.2", []int{1, 2}},
{"bd-af78e9a2", []int{2, 3}}, // counter was 1 after creating .1
{"bd-af78e9a2.1", []int{3, 4}}, // counter was 2 after creating .1.2
{"bd-af78e9a2.1.2", []int{1, 2}}, // no children created, starts at 1
}
for _, tt := range tests {
for _, want := range tt.expected {
got, err := store.getNextChildNumber(ctx, tt.parent)