// migrate-to-sql - Script para migrar dados do BoltDB para arquivo SQL package main import ( "crypto/sha256" "encoding/hex" "flag" "fmt" "log" "math/rand" "os" "strings" "time" "github.com/boltdb/bolt" ) 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 verificando contra um mapa func generateUniqueShortID(usedIDs map[string]bool) string { maxAttempts := 100 for i := 0; i < maxAttempts; i++ { id := generateShortID() if !usedIDs[id] { return id } } return "" } func main() { // Initialize random seed rand.Seed(time.Now().UnixNano()) var boltPath, sqlOutputPath string flag.StringVar(&boltPath, "bolt", "", "caminho para o arquivo BoltDB") flag.StringVar(&sqlOutputPath, "output", "", "caminho para o arquivo SQL de saída") flag.Parse() if boltPath == "" || sqlOutputPath == "" { fmt.Fprintf(os.Stderr, "Uso: %s -bolt -output \n", os.Args[0]) os.Exit(1) } // Verificar se o arquivo BoltDB existe if _, err := os.Stat(boltPath); os.IsNotExist(err) { log.Fatal("Arquivo BoltDB não encontrado:", boltPath) } // 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() // Criar arquivo SQL de saída sqlFile, err := os.Create(sqlOutputPath) if err != nil { log.Fatal("Erro ao criar arquivo SQL:", err) } defer sqlFile.Close() // Escrever cabeçalho do arquivo SQL sqlFile.WriteString(`-- Script de migração do BoltDB para SQLite3 -- Gerado automaticamente em ` + time.Now().Format("02/01/2006 15:04:05") + ` -- Criar tabela se não existir 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 ); -- Criar índices CREATE INDEX IF NOT EXISTS idx_pastes_content_hash ON pastes(content_hash); CREATE INDEX IF NOT EXISTS idx_pastes_created_at ON pastes(created_at); -- Inserir dados migrados `) // Mapear hashes para IDs curtos para evitar duplicatas hashToShortID := make(map[string]string) usedShortIDs := make(map[string]bool) // Contadores totalPastes := 0 duplicates := 0 insertCount := 0 // Migrar dados do BoltDB 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 { totalPastes++ // 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 if existingShortID, exists := hashToShortID[hashStr]; exists { // Paste já existe, usar o mesmo ID curto duplicates++ fmt.Printf("Duplicata encontrada: %s -> %s\n", hashStr[:8], existingShortID) return nil } // Gerar ID curto único shortID := generateUniqueShortID(usedShortIDs) if shortID == "" { return fmt.Errorf("não foi possível gerar ID único para paste %d", totalPastes) } // Marcar ID como usado usedShortIDs[shortID] = true hashToShortID[hashStr] = shortID // Preparar conteúdo para SQL (escapar aspas) content := strings.ReplaceAll(string(v), "'", "''") // Gerar timestamp (usar timestamp atual para todos) timestamp := time.Now().Unix() // Escrever comando INSERT insertSQL := fmt.Sprintf("INSERT OR IGNORE INTO pastes (short_id, content, content_hash, created_at) VALUES ('%s', '%s', '%s', %d);\n", shortID, content, hashStr, timestamp) _, err := sqlFile.WriteString(insertSQL) if err != nil { return fmt.Errorf("erro ao escrever SQL: %v", err) } insertCount++ // Progresso a cada 100 pastes if totalPastes%100 == 0 { fmt.Printf("Processados %d pastes...\n", totalPastes) } return nil }) }) if err != nil { log.Fatal("Erro durante migração:", err) } // Escrever comentários finais sqlFile.WriteString(fmt.Sprintf(` -- Resumo da migração: -- Total de pastes processados: %d -- Pastes únicos inseridos: %d -- Duplicatas ignoradas: %d -- Arquivo gerado em: %s `, totalPastes, insertCount, duplicates, time.Now().Format("02/01/2006 15:04:05"))) fmt.Printf("\n=== Migração concluída! ===\n") fmt.Printf("Total de pastes processados: %d\n", totalPastes) fmt.Printf("Pastes únicos inseridos: %d\n", insertCount) fmt.Printf("Duplicatas ignoradas: %d\n", duplicates) fmt.Printf("Arquivo SQL gerado: %s\n", sqlOutputPath) fmt.Printf("\nPara aplicar a migração:\n") fmt.Printf("sqlite3 data/yasuc.sqlite3 < %s\n", sqlOutputPath) }