// migrate - Script para migrar dados do BoltDB para SQLite3 package main import ( "crypto/sha256" "encoding/hex" "flag" "fmt" "log" "math/rand" "os" "time" "database/sql" "github.com/boltdb/bolt" _ "modernc.org/sqlite" ) const shortIDLength = 7 // generateShortID gera um ID único de 7 caracteres func generateShortID() string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, shortIDLength) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) } // generateUniqueShortID gera um ID único que não existe no banco func generateUniqueShortID(db *sql.DB) (string, error) { maxAttempts := 100 for i := 0; i < maxAttempts; i++ { id := generateShortID() // Verificar se o ID já existe var exists int err := db.QueryRow("SELECT 1 FROM pastes WHERE short_id = ? LIMIT 1", id).Scan(&exists) if err == sql.ErrNoRows { return id, nil } if err != nil { return "", err } } return "", fmt.Errorf("não foi possível gerar um ID único após %d tentativas", maxAttempts) } func main() { // Initialize random seed rand.Seed(time.Now().UnixNano()) var boltPath, sqlitePath string flag.StringVar(&boltPath, "bolt", "", "caminho para o arquivo BoltDB") flag.StringVar(&sqlitePath, "sqlite", "", "caminho para o arquivo SQLite3 de destino") flag.Parse() if boltPath == "" || sqlitePath == "" { fmt.Fprintf(os.Stderr, "Uso: %s -bolt -sqlite \n", os.Args[0]) os.Exit(1) } // Abrir banco BoltDB boltDB, err := bolt.Open(boltPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { log.Fatal("Erro ao abrir BoltDB:", err) } defer boltDB.Close() // Abrir banco SQLite3 sqliteDB, err := sql.Open("sqlite", sqlitePath) if err != nil { log.Fatal("Erro ao abrir SQLite3:", err) } defer sqliteDB.Close() // Criar tabela no SQLite3 _, err = sqliteDB.Exec(` CREATE TABLE IF NOT EXISTS pastes ( short_id TEXT PRIMARY KEY, content TEXT NOT NULL, content_hash TEXT NOT NULL, created_at INTEGER NOT NULL ) `) if err != nil { log.Fatal("Erro ao criar tabela:", err) } // Criar índices _, err = sqliteDB.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_content_hash ON pastes(content_hash)`) if err != nil { log.Fatal("Erro ao criar índice content_hash:", err) } _, err = sqliteDB.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_created_at ON pastes(created_at)`) if err != nil { log.Fatal("Erro ao criar índice created_at:", err) } // Iniciar transação no SQLite3 tx, err := sqliteDB.Begin() if err != nil { log.Fatal("Erro ao iniciar transação:", err) } // Preparar statement de inserção stmt, err := tx.Prepare("INSERT OR IGNORE INTO pastes (short_id, content, content_hash, created_at) VALUES (?, ?, ?, ?)") if err != nil { log.Fatal("Erro ao preparar statement:", err) } defer stmt.Close() // Migrar dados do BoltDB para SQLite3 count := 0 duplicates := 0 err = boltDB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("pastes")) if b == nil { return fmt.Errorf("bucket 'pastes' não encontrado") } return b.ForEach(func(k, v []byte) error { // Calcular hash SHA-256 do conteúdo hash := sha256.Sum256(v) hashStr := hex.EncodeToString(hash[:]) // Verificar se já existe um paste com o mesmo conteúdo var existingID string err := sqliteDB.QueryRow("SELECT short_id FROM pastes WHERE content_hash = ? LIMIT 1", hashStr).Scan(&existingID) if err == nil { // Paste já existe, pular duplicates++ return nil } else if err != sql.ErrNoRows { return fmt.Errorf("erro ao verificar duplicata: %v", err) } // Gerar ID curto único shortID, err := generateUniqueShortID(sqliteDB) if err != nil { return fmt.Errorf("erro ao gerar ID único: %v", err) } // Inserir no SQLite3 _, err = stmt.Exec(shortID, string(v), hashStr, time.Now().Unix()) if err != nil { return fmt.Errorf("erro ao inserir paste %s: %v", shortID, err) } count++ if count%100 == 0 { fmt.Printf("Migrados %d pastes...\n", count) } return nil }) }) if err != nil { tx.Rollback() log.Fatal("Erro durante migração:", err) } // Commit da transação err = tx.Commit() if err != nil { log.Fatal("Erro ao fazer commit:", err) } fmt.Printf("Migração concluída! %d pastes migrados com sucesso, %d duplicatas ignoradas.\n", count, duplicates) }