// migrate/main.go — SQLite → MySQL one-api data migration tool // Usage: go run tools/migrate/main.go package main import ( "database/sql" "fmt" "log" "os" _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" ) const ( sqlitePath = "one-api.db" mysqlDSN = "root:123456@tcp(localhost:3306)/oneapi?charset=utf8mb4&parseTime=True&loc=Local" ) func main() { if _, err := os.Stat(sqlitePath); os.IsNotExist(err) { log.Fatalf("SQLite file not found: %s", sqlitePath) } sqlite, err := sql.Open("sqlite3", sqlitePath) if err != nil { log.Fatalf("open sqlite: %v", err) } defer sqlite.Close() mysql, err := sql.Open("mysql", mysqlDSN) if err != nil { log.Fatalf("open mysql: %v", err) } defer mysql.Close() if err = mysql.Ping(); err != nil { log.Fatalf("mysql ping failed: %v\nCheck if MySQL is running and DSN is correct.", err) } fmt.Println("Connected to both databases. Starting migration...") tables := []string{"users", "channels", "tokens", "options", "redemptions", "logs", "abilities"} for _, table := range tables { if err := migrateTable(sqlite, mysql, table); err != nil { fmt.Printf("[WARN] table %s: %v\n", table, err) } } fmt.Println("\nMigration complete!") } func migrateTable(src, dst *sql.DB, table string) error { // Check table exists in SQLite var count int err := src.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'", table)).Scan(&count) if err != nil || count == 0 { fmt.Printf("[SKIP] table %s not found in SQLite\n", table) return nil } // Get row count var total int _ = src.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM `%s`", table)).Scan(&total) if total == 0 { fmt.Printf("[SKIP] table %s is empty\n", table) return nil } // Read all rows rows, err := src.Query(fmt.Sprintf("SELECT * FROM `%s`", table)) if err != nil { return fmt.Errorf("select from sqlite: %w", err) } defer rows.Close() cols, err := rows.Columns() if err != nil { return err } // Build INSERT statement with placeholders placeholders := "" colNames := "" for i, col := range cols { if i > 0 { placeholders += "," colNames += "," } placeholders += "?" colNames += fmt.Sprintf("`%s`", col) } insertSQL := fmt.Sprintf("INSERT IGNORE INTO `%s` (%s) VALUES (%s)", table, colNames, placeholders) stmt, err := dst.Prepare(insertSQL) if err != nil { return fmt.Errorf("prepare insert for %s: %w", table, err) } defer stmt.Close() inserted := 0 vals := make([]interface{}, len(cols)) valPtrs := make([]interface{}, len(cols)) for i := range vals { valPtrs[i] = &vals[i] } for rows.Next() { if err := rows.Scan(valPtrs...); err != nil { return fmt.Errorf("scan: %w", err) } // Convert []byte to string for MySQL compatibility args := make([]interface{}, len(vals)) for i, v := range vals { if b, ok := v.([]byte); ok { args[i] = string(b) } else { args[i] = v } } if _, err := stmt.Exec(args...); err != nil { fmt.Printf("[WARN] insert row in %s: %v\n", table, err) continue } inserted++ } fmt.Printf("[OK] %s: %d/%d rows migrated\n", table, inserted, total) return nil }