Files
beads/internal/storage/dolt/config.go
mayor 1dc36098a3 feat(storage): add Dolt backend for version-controlled issue storage
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>
2026-01-14 21:06:10 -08:00

111 lines
3.0 KiB
Go

package dolt
import (
"context"
"database/sql"
"fmt"
"strings"
)
// SetConfig sets a configuration value
func (s *DoltStore) SetConfig(ctx context.Context, key, value string) error {
_, err := s.db.ExecContext(ctx, `
INSERT INTO config (` + "`key`" + `, value) VALUES (?, ?)
ON DUPLICATE KEY UPDATE value = VALUES(value)
`, key, value)
if err != nil {
return fmt.Errorf("failed to set config %s: %w", key, err)
}
return nil
}
// GetConfig retrieves a configuration value
func (s *DoltStore) GetConfig(ctx context.Context, key string) (string, error) {
var value string
err := s.db.QueryRowContext(ctx, "SELECT value FROM config WHERE `key` = ?", key).Scan(&value)
if err == sql.ErrNoRows {
return "", nil
}
if err != nil {
return "", fmt.Errorf("failed to get config %s: %w", key, err)
}
return value, nil
}
// GetAllConfig retrieves all configuration values
func (s *DoltStore) GetAllConfig(ctx context.Context) (map[string]string, error) {
rows, err := s.db.QueryContext(ctx, "SELECT `key`, value FROM config")
if err != nil {
return nil, fmt.Errorf("failed to get all config: %w", err)
}
defer rows.Close()
config := make(map[string]string)
for rows.Next() {
var key, value string
if err := rows.Scan(&key, &value); err != nil {
return nil, fmt.Errorf("failed to scan config: %w", err)
}
config[key] = value
}
return config, rows.Err()
}
// DeleteConfig removes a configuration value
func (s *DoltStore) DeleteConfig(ctx context.Context, key string) error {
_, err := s.db.ExecContext(ctx, "DELETE FROM config WHERE `key` = ?", key)
if err != nil {
return fmt.Errorf("failed to delete config %s: %w", key, err)
}
return nil
}
// SetMetadata sets a metadata value
func (s *DoltStore) SetMetadata(ctx context.Context, key, value string) error {
_, err := s.db.ExecContext(ctx, `
INSERT INTO metadata (` + "`key`" + `, value) VALUES (?, ?)
ON DUPLICATE KEY UPDATE value = VALUES(value)
`, key, value)
if err != nil {
return fmt.Errorf("failed to set metadata %s: %w", key, err)
}
return nil
}
// GetMetadata retrieves a metadata value
func (s *DoltStore) GetMetadata(ctx context.Context, key string) (string, error) {
var value string
err := s.db.QueryRowContext(ctx, "SELECT value FROM metadata WHERE `key` = ?", key).Scan(&value)
if err == sql.ErrNoRows {
return "", nil
}
if err != nil {
return "", fmt.Errorf("failed to get metadata %s: %w", key, err)
}
return value, nil
}
// GetCustomStatuses returns custom status values from config
func (s *DoltStore) GetCustomStatuses(ctx context.Context) ([]string, error) {
value, err := s.GetConfig(ctx, "status.custom")
if err != nil {
return nil, err
}
if value == "" {
return nil, nil
}
return strings.Split(value, ","), nil
}
// GetCustomTypes returns custom issue type values from config
func (s *DoltStore) GetCustomTypes(ctx context.Context) ([]string, error) {
value, err := s.GetConfig(ctx, "types.custom")
if err != nil {
return nil, err
}
if value == "" {
return nil, nil
}
return strings.Split(value, ","), nil
}