Implements a complete Dolt storage backend that mirrors the SQLite implementation with MySQL-compatible syntax and adds version control capabilities. Key features: - Full Storage interface implementation (~50 methods) - Version control operations: commit, push, pull, branch, merge, checkout - History queries via AS OF and dolt_history_* tables - Cell-level merge instead of line-level JSONL merge - SQL injection protection with input validation Bug fixes applied during implementation: - Added missing quality_score, work_type, source_system to scanIssue - Fixed Status() to properly parse boolean staged column - Added validation to CreateIssues (was missing in batch create) - Made RenameDependencyPrefix transactional - Expanded GetIssueHistory to return more complete data Test coverage: 17 tests covering CRUD, dependencies, labels, search, comments, events, statistics, and SQL injection protection. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
package dolt
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// GetDirtyIssues returns IDs of issues that have been modified since last export
|
|
func (s *DoltStore) GetDirtyIssues(ctx context.Context) ([]string, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT issue_id FROM dirty_issues ORDER BY marked_at ASC
|
|
`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get dirty issues: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var ids []string
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, fmt.Errorf("failed to scan issue id: %w", err)
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
return ids, rows.Err()
|
|
}
|
|
|
|
// GetDirtyIssueHash returns the dirty hash for a specific issue
|
|
func (s *DoltStore) GetDirtyIssueHash(ctx context.Context, issueID string) (string, error) {
|
|
var hash string
|
|
err := s.db.QueryRowContext(ctx, `
|
|
SELECT i.content_hash FROM issues i
|
|
JOIN dirty_issues d ON i.id = d.issue_id
|
|
WHERE d.issue_id = ?
|
|
`, issueID).Scan(&hash)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get dirty issue hash: %w", err)
|
|
}
|
|
return hash, nil
|
|
}
|
|
|
|
// ClearDirtyIssuesByID removes specific issues from the dirty list
|
|
func (s *DoltStore) ClearDirtyIssuesByID(ctx context.Context, issueIDs []string) error {
|
|
if len(issueIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
placeholders := make([]string, len(issueIDs))
|
|
args := make([]interface{}, len(issueIDs))
|
|
for i, id := range issueIDs {
|
|
placeholders[i] = "?"
|
|
args[i] = id
|
|
}
|
|
|
|
query := fmt.Sprintf("DELETE FROM dirty_issues WHERE issue_id IN (%s)", strings.Join(placeholders, ","))
|
|
_, err := s.db.ExecContext(ctx, query, args...)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to clear dirty issues: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetExportHash returns the last export hash for an issue
|
|
func (s *DoltStore) GetExportHash(ctx context.Context, issueID string) (string, error) {
|
|
var hash string
|
|
err := s.db.QueryRowContext(ctx, `
|
|
SELECT content_hash FROM export_hashes WHERE issue_id = ?
|
|
`, issueID).Scan(&hash)
|
|
if err != nil {
|
|
return "", nil // Not found is OK
|
|
}
|
|
return hash, nil
|
|
}
|
|
|
|
// SetExportHash stores the export hash for an issue
|
|
func (s *DoltStore) SetExportHash(ctx context.Context, issueID, contentHash string) error {
|
|
_, err := s.db.ExecContext(ctx, `
|
|
INSERT INTO export_hashes (issue_id, content_hash, exported_at)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE content_hash = VALUES(content_hash), exported_at = VALUES(exported_at)
|
|
`, issueID, contentHash, time.Now())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set export hash: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearAllExportHashes removes all export hashes (for full re-export)
|
|
func (s *DoltStore) ClearAllExportHashes(ctx context.Context) error {
|
|
_, err := s.db.ExecContext(ctx, "DELETE FROM export_hashes")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to clear export hashes: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetJSONLFileHash returns the stored JSONL file hash
|
|
func (s *DoltStore) GetJSONLFileHash(ctx context.Context) (string, error) {
|
|
return s.GetMetadata(ctx, "jsonl_file_hash")
|
|
}
|
|
|
|
// SetJSONLFileHash stores the JSONL file hash
|
|
func (s *DoltStore) SetJSONLFileHash(ctx context.Context, fileHash string) error {
|
|
return s.SetMetadata(ctx, "jsonl_file_hash", fileHash)
|
|
}
|