Skip to content

Commit 680a96b

Browse files
author
Developer
committed
fix: auto-discover mysqlbinlog path and fix hardcoded EarliestTime
Auto-discover mysqlbinlog from PATH, MySQL basedir, or common installation paths instead of relying solely on PATH. Also remove the hardcoded 24h placeholder for EarliestTime.
1 parent 97085ef commit 680a96b

3 files changed

Lines changed: 66 additions & 7 deletions

File tree

internal/connector/mysql.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,22 @@ func (m *MySQLConnector) GetBinlogDir(ctx context.Context) (string, error) {
184184
}
185185

186186
// ---------------------------------------------------------------------------
187-
// ParseBinlog
187+
// GetBasedir
188+
// ---------------------------------------------------------------------------
189+
190+
func (m *MySQLConnector) GetBasedir(ctx context.Context) (string, error) {
191+
if m.db == nil {
192+
return "", fmt.Errorf("connector: not connected")
193+
}
194+
var name, basedir string
195+
if err := m.db.QueryRowContext(ctx, "SHOW VARIABLES LIKE 'basedir'").Scan(&name, &basedir); err != nil {
196+
return "", fmt.Errorf("connector: query basedir: %w", err)
197+
}
198+
if basedir == "" {
199+
return "", fmt.Errorf("connector: basedir is empty")
200+
}
201+
return basedir, nil
202+
}
188203
// ---------------------------------------------------------------------------
189204
// ParseBinlog verifies that the requested binlog files exist and returns any
190205
// parse errors. Actual event-level parsing is delegated to the parser package

internal/server/pitr/handler.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,49 @@ func (h *Handler) Progress(w http.ResponseWriter, r *http.Request) {
497497
})
498498
}
499499

500+
// findMySQLBinlog locates the mysqlbinlog binary by checking PATH first, then
501+
// querying MySQL's basedir, and finally checking common installation paths.
502+
func findMySQLBinlog(ctx context.Context, conn *connector.MySQLConnector) (string, error) {
503+
// 1. Try PATH first.
504+
if path, err := exec.LookPath("mysqlbinlog"); err == nil {
505+
return path, nil
506+
}
507+
508+
// 2. Try MySQL basedir (SHOW VARIABLES LIKE 'basedir').
509+
basedir, err := conn.GetBasedir(ctx)
510+
if err == nil && basedir != "" {
511+
for _, name := range []string{"mysqlbinlog", "mysqlbinlog.exe"} {
512+
candidate := filepath.Join(basedir, "bin", name)
513+
if fi, statErr := os.Stat(candidate); statErr == nil && fi.Mode().IsRegular() {
514+
abs, _ := filepath.Abs(candidate)
515+
return abs, nil
516+
}
517+
}
518+
}
519+
520+
// 3. Common installation paths.
521+
commonPaths := []string{
522+
"/usr/bin/mysqlbinlog",
523+
"/usr/local/mysql/bin/mysqlbinlog",
524+
"/opt/homebrew/bin/mysqlbinlog",
525+
"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysqlbinlog.exe",
526+
"C:\\Program Files\\MySQL\\MySQL Server 8.4\\bin\\mysqlbinlog.exe",
527+
"C:\\Program Files\\MySQL\\MySQL Server 9.0\\bin\\mysqlbinlog.exe",
528+
}
529+
for _, p := range commonPaths {
530+
if fi, err := os.Stat(p); err == nil && fi.Mode().IsRegular() {
531+
abs, _ := filepath.Abs(p)
532+
return abs, nil
533+
}
534+
}
535+
536+
return "", fmt.Errorf("mysqlbinlog not found in $PATH, MySQL basedir, or common installation paths; install mysql-client or add mysqlbinlog to your PATH")
537+
}
538+
500539
// parseBinlogRemote uses mysqlbinlog --read-from-remote-server to parse binlog
501540
// files from a remote MySQL server. This is the fallback when binlog files are
502541
// not accessible on the local filesystem.
503-
func (h *Handler) parseBinlogRemote(cfg connector.ConnConfig, binlogNames []string, targetTable string, recoveryTime time.Time) (*connector.ParseResult, error) {
542+
func (h *Handler) parseBinlogRemote(mysqlbinlogPath string, cfg connector.ConnConfig, binlogNames []string, targetTable string, recoveryTime time.Time) (*connector.ParseResult, error) {
504543
parts := strings.SplitN(targetTable, ".", 2)
505544
if len(parts) != 2 {
506545
return nil, fmt.Errorf("invalid target table %q: expected schema.table format", targetTable)
@@ -519,8 +558,8 @@ func (h *Handler) parseBinlogRemote(cfg connector.ConnConfig, binlogNames []stri
519558
"--protocol=TCP",
520559
}
521560
args = append(args, binlogNames...)
522-
523-
cmd := exec.Command("mysqlbinlog", args...)
561+
562+
cmd := exec.Command(mysqlbinlogPath, args...)
524563
stdout, err := cmd.StdoutPipe()
525564
if err != nil {
526565
return nil, fmt.Errorf("mysqlbinlog pipe: %w", err)
@@ -740,7 +779,7 @@ func (h *Handler) runOperation(op *Operation, operator string) {
740779
op.PreflightRes = &PreflightResult{
741780
CheckedAt: time.Now(),
742781
BinlogFiles: binlogNames,
743-
EarliestTime: time.Now().Add(-24 * time.Hour),
782+
EarliestTime: time.Time{},
744783
EstimatedSize: totalSize,
745784
}
746785
_ = h.opStore.Update(op)
@@ -758,7 +797,12 @@ func (h *Handler) runOperation(op *Operation, operator string) {
758797
if len(paths) > 0 {
759798
if _, statErr := os.Stat(paths[0]); os.IsNotExist(statErr) {
760799
log.Printf("pitr: binlog files not accessible locally, trying mysqlbinlog remote for op %s", op.ID)
761-
parseRes, err = h.parseBinlogRemote(connCfg, binlogNames, op.TargetTable, op.RecoveryTime)
800+
mysqlbinlogPath, findErr := findMySQLBinlog(ctx, conn)
801+
if findErr != nil {
802+
h.failOperation(op, "parse binlogs (remote): %v", findErr)
803+
return
804+
}
805+
parseRes, err = h.parseBinlogRemote(mysqlbinlogPath, connCfg, binlogNames, op.TargetTable, op.RecoveryTime)
762806
if err != nil {
763807
h.failOperation(op, "parse binlogs (remote): %v", err)
764808
return

internal/server/pitr/store.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type Operation struct {
3232
type PreflightResult struct {
3333
CheckedAt time.Time `json:"checkedAt"`
3434
BinlogFiles []string `json:"binlogFiles"`
35-
EarliestTime time.Time `json:"earliestTime"`
35+
EarliestTime time.Time `json:"earliestTime,omitempty"`
3636
EstimatedSize int64 `json:"estimatedSize"`
3737
}
3838

0 commit comments

Comments
 (0)