141 lines
3.2 KiB
Go
141 lines
3.2 KiB
Go
package replica
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"os"
|
|
|
|
"github.com/go-mysql-org/go-mysql/mysql"
|
|
)
|
|
|
|
// PositionManager handles binlog position persistence
|
|
type PositionManager struct {
|
|
db *sql.DB
|
|
positionFile string
|
|
}
|
|
|
|
// NewPositionManager creates a new position manager
|
|
func NewPositionManager(db *sql.DB, positionFile string) *PositionManager {
|
|
return &PositionManager{
|
|
db: db,
|
|
positionFile: positionFile,
|
|
}
|
|
}
|
|
|
|
// InitTable creates the binlog_position table if it doesn't exist
|
|
func (pm *PositionManager) InitTable() error {
|
|
if pm.db == nil {
|
|
return nil
|
|
}
|
|
|
|
createTableSQL := `
|
|
CREATE TABLE IF NOT EXISTS binlog_position (
|
|
id INT PRIMARY KEY,
|
|
log_file VARCHAR(255),
|
|
log_pos BIGINT,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
)
|
|
`
|
|
|
|
_, err := pm.db.Exec(createTableSQL)
|
|
if err != nil {
|
|
Errorf("[ERROR] Failed to create binlog_position table: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Load loads the last saved position from database or file
|
|
func (pm *PositionManager) Load() (mysql.Position, error) {
|
|
if pm.db != nil {
|
|
return pm.loadFromDB()
|
|
}
|
|
return pm.loadFromFile()
|
|
}
|
|
|
|
func (pm *PositionManager) loadFromDB() (mysql.Position, error) {
|
|
var name string
|
|
var pos uint32
|
|
|
|
err := pm.db.QueryRow(
|
|
"SELECT log_file, log_pos FROM binlog_position WHERE id = 1",
|
|
).Scan(&name, &pos)
|
|
|
|
if err == sql.ErrNoRows {
|
|
return mysql.Position{Name: "", Pos: 0}, nil
|
|
}
|
|
if err != nil {
|
|
return mysql.Position{Name: "", Pos: 0}, err
|
|
}
|
|
|
|
Infof("[INFO] Loaded position: %s:%d", name, pos)
|
|
return mysql.Position{Name: name, Pos: pos}, nil
|
|
}
|
|
|
|
func (pm *PositionManager) loadFromFile() (mysql.Position, error) {
|
|
data, err := os.ReadFile(pm.positionFile)
|
|
if err != nil {
|
|
return mysql.Position{Name: "", Pos: 0}, nil
|
|
}
|
|
|
|
var pos mysql.Position
|
|
err = json.Unmarshal(data, &pos)
|
|
return pos, err
|
|
}
|
|
|
|
// Save saves the current position to database or file
|
|
func (pm *PositionManager) Save(pos mysql.Position) error {
|
|
if pm.db != nil {
|
|
return pm.saveToDB(pos)
|
|
}
|
|
return pm.saveToFile(pos)
|
|
}
|
|
|
|
// Reset clears the saved position
|
|
func (pm *PositionManager) Reset() error {
|
|
if pm.db != nil {
|
|
return pm.resetInDB()
|
|
}
|
|
return pm.resetInFile()
|
|
}
|
|
|
|
func (pm *PositionManager) resetInDB() error {
|
|
_, err := pm.db.Exec("DELETE FROM binlog_position WHERE id = 1")
|
|
return err
|
|
}
|
|
|
|
func (pm *PositionManager) resetInFile() error {
|
|
return os.Remove(pm.positionFile)
|
|
}
|
|
|
|
func (pm *PositionManager) saveToDB(pos mysql.Position) error {
|
|
result, err := pm.db.Exec(
|
|
"INSERT INTO binlog_position (id, log_file, log_pos) VALUES (1, ?, ?) "+
|
|
"ON DUPLICATE KEY UPDATE log_file = VALUES(log_file), log_pos = VALUES(log_pos)",
|
|
pos.Name, pos.Pos,
|
|
)
|
|
|
|
if err != nil {
|
|
Errorf("[ERROR] Failed to save position: %v", err)
|
|
return err
|
|
}
|
|
|
|
rowsAffected, _ := result.RowsAffected()
|
|
Infof("[INFO] Saved position: %s:%d (rows: %d)", pos.Name, pos.Pos, rowsAffected)
|
|
return nil
|
|
}
|
|
|
|
func (pm *PositionManager) saveToFile(pos mysql.Position) error {
|
|
data, err := json.Marshal(pos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(pm.positionFile, data, 0644)
|
|
}
|
|
|
|
// GetPositionFile returns the position file path
|
|
func (pm *PositionManager) GetPositionFile() string {
|
|
return pm.positionFile
|
|
}
|