diff --git a/internal/storage/sqlite/dependencies.go b/internal/storage/sqlite/dependencies.go index aa6dea96..e7d8a7ab 100644 --- a/internal/storage/sqlite/dependencies.go +++ b/internal/storage/sqlite/dependencies.go @@ -691,7 +691,7 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type var externalRef sql.NullString var sourceRepo sql.NullString var closeReason sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -729,9 +729,7 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type if closeReason.Valid { issue.CloseReason = closeReason.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String } @@ -773,7 +771,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows * var assignee sql.NullString var externalRef sql.NullString var sourceRepo sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -810,9 +808,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows * if sourceRepo.Valid { issue.SourceRepo = sourceRepo.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String } diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 1fc59c6e..bc48003b 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -11,6 +11,23 @@ import ( "github.com/steveyegge/beads/internal/types" ) +// parseNullableTimeString parses a nullable time string from database TEXT columns. +// The ncruces/go-sqlite3 driver only auto-converts TEXT→time.Time for columns declared +// as DATETIME/DATE/TIME/TIMESTAMP. For TEXT columns (like deleted_at), we must parse manually. +// Supports RFC3339, RFC3339Nano, and SQLite's native format. +func parseNullableTimeString(ns sql.NullString) *time.Time { + if !ns.Valid || ns.String == "" { + return nil + } + // Try RFC3339Nano first (more precise), then RFC3339, then SQLite format + for _, layout := range []string{time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05"} { + if t, err := time.Parse(layout, ns.String); err == nil { + return &t + } + } + return nil // Unparseable - shouldn't happen with valid data +} + // REMOVED (bd-8e05): getNextIDForPrefix and AllocateNextID - sequential ID generation // no longer needed with hash-based IDs // Migration functions moved to migrations.go (bd-fc2d, bd-b245) @@ -158,7 +175,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue, var originalSize sql.NullInt64 var sourceRepo sql.NullString var closeReason sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -220,9 +237,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue, if closeReason.Valid { issue.CloseReason = closeReason.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String } @@ -331,7 +346,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s var compactedAtCommit sql.NullString var sourceRepo sql.NullString var closeReason sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -391,9 +406,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s if closeReason.Valid { issue.CloseReason = closeReason.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String } diff --git a/internal/storage/sqlite/ready.go b/internal/storage/sqlite/ready.go index e4d2a297..1865a96f 100644 --- a/internal/storage/sqlite/ready.go +++ b/internal/storage/sqlite/ready.go @@ -170,7 +170,7 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi var compactedAtCommit sql.NullString var originalSize sql.NullInt64 var closeReason sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -221,9 +221,7 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi if closeReason.Valid { issue.CloseReason = closeReason.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String } diff --git a/internal/storage/sqlite/transaction.go b/internal/storage/sqlite/transaction.go index 9689ee8b..96b89f10 100644 --- a/internal/storage/sqlite/transaction.go +++ b/internal/storage/sqlite/transaction.go @@ -1068,7 +1068,7 @@ func scanIssueRow(row scanner) (*types.Issue, error) { var sourceRepo sql.NullString var compactedAtCommit sql.NullString var closeReason sql.NullString - var deletedAt sql.NullTime + var deletedAt sql.NullString // TEXT column, not DATETIME - must parse manually var deletedBy sql.NullString var deleteReason sql.NullString var originalType sql.NullString @@ -1116,9 +1116,7 @@ func scanIssueRow(row scanner) (*types.Issue, error) { if closeReason.Valid { issue.CloseReason = closeReason.String } - if deletedAt.Valid { - issue.DeletedAt = &deletedAt.Time - } + issue.DeletedAt = parseNullableTimeString(deletedAt) if deletedBy.Valid { issue.DeletedBy = deletedBy.String }