package main import ( "database/sql" "fmt" "log" "os" "strings" "time" _ "github.com/go-sql-driver/mysql" "gopkg.in/yaml.v2" ) type Cfg struct { Settings Settings `yaml:"Settings"` DBConfig DBConfig `yaml:"DBConfig"` FormatColumns FormatColumns `yaml:"FormatColumns"` } type Settings struct { Condition string `yaml:"Condition"` PrimaryKey string `yaml:"PrimaryKey"` DeleteSource bool `yaml:"DeleteSource"` } type FormatColumns struct { Enablement bool `yaml:"Enablement"` Reference string `yaml:"Reference"` Year string `yaml:"Year"` Month string `yaml:"Month"` Day string `yaml:"Day"` Week string `yaml:"Week"` } type DBConfig struct { Username string `yaml:"Username"` Password string `yaml:"Password"` Server string `yaml:"Server"` Port string `yaml:"Port"` Database string `yaml:"Database"` SourceTable string `yaml:"SourceTable"` DestinationTable string `yaml:"DestinationTable"` SourceColumns []string `yaml:"SourceColumns"` DestinationColumns []string `yaml:"DestinationColumns"` } var cfg = Cfg{} var dsn string func main() { log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) log.SetOutput(os.Stdout) self := strings.TrimSuffix(os.Args[0], ".exe") file := self + ".yml" txt, err := os.ReadFile(file) if err != nil { log.Printf("[ERR] %s\r\n", err.Error()) os.Exit(1) } err = yaml.Unmarshal(txt, &cfg) if err != nil { log.Printf("[ERR] %s\r\n", err.Error()) os.Exit(1) } // srcCols := cfg.DBConfig.Columns // dstCols := cfg.DBConfig.Columns // if len(srcCols) != len(dstCols) { // log.Println("[ERR] Mismatched Length Of Source Columns And Destination Columns.") // os.Exit(1) // } condition := cfg.Settings.Condition log.Printf("[MSG] Condition: %s\r\n", condition) if condition == "" { log.Println("[ERR] Missing Query Condition.") os.Exit(1) } dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", cfg.DBConfig.Username, cfg.DBConfig.Password, cfg.DBConfig.Server, cfg.DBConfig.Port, cfg.DBConfig.Database, ) qry, err := retrieveData(condition) if err != nil { log.Printf("[ERR] %v\r\n", err) os.Exit(1) } log.Printf("[MSG] Selected Rows: %d\r\n", len(qry)) if len(qry) == 0 { os.Exit(0) } err = backupData(qry) if err != nil { log.Printf("[ERR] %v\r\n", err) os.Exit(1) } } func retrieveData(condition string) ([]map[string]interface{}, error) { rst := make([]map[string]interface{}, 0) dbo, err := sql.Open("mysql", dsn) if err != nil { return rst, err } defer dbo.Close() err = dbo.Ping() if err != nil { return rst, err } cols := "" for _, c := range cfg.DBConfig.SourceColumns { cols = cols + c + "," } cols = strings.TrimSuffix(cols, ",") qry := fmt.Sprintf("SELECT %s FROM %s.%s", cols, cfg.DBConfig.Database, cfg.DBConfig.SourceTable, ) flt := fmt.Sprintf("WHERE %s;", condition) if condition != "" { qry = qry + " " + flt } lnth := len(cfg.DBConfig.SourceColumns) vals := make([]interface{}, lnth) valsPtr := make([]interface{}, lnth) for i := 0; i < lnth; i++ { valsPtr[i] = &vals[i] } rows, err := dbo.Query(qry) if err != nil { return rst, err } defer rows.Close() for rows.Next() { err := rows.Scan(valsPtr...) if err != nil { break } row := make(map[string]interface{}, 0) for i, val := range vals { key := cfg.DBConfig.SourceColumns[i] var v interface{} b, ok := val.([]byte) if ok { v = string(b) } else { v = val } row[key] = v } rst = append(rst, row) } return rst, nil } func backupData(data []map[string]interface{}) error { dbo, err := sql.Open("mysql", dsn) if err != nil { return err } defer dbo.Close() err = dbo.Ping() if err != nil { return err } cols := "" for _, c := range cfg.DBConfig.DestinationColumns { cols = cols + c + "," } // Append Formated Column Keys if cfg.FormatColumns.Enablement { cols = cols + strings.Join( []string{ cfg.FormatColumns.Year, cfg.FormatColumns.Month, cfg.FormatColumns.Day, cfg.FormatColumns.Week, }, ",", ) } cols = strings.TrimSuffix(cols, ",") var timeStr string = "" var timeVal time.Time for i, d := range data { log.Printf("[MSG] #%d: %#v\r\n", i+1, d) vals := "" for _, c := range cfg.DBConfig.DestinationColumns { for k, v := range d { if k == c { vals = vals + fmt.Sprintf("%#v,", v) } if k == cfg.FormatColumns.Reference { timeStr = v.(string) timeVal, err = time.Parse("2006-01-02 15:04:05", timeStr) if err != nil { log.Printf("[ERR] #%d: %#v\r\n", i+1, err) continue } } } } // Append Formated Column Values if cfg.FormatColumns.Enablement { vals = vals + strings.Join( []string{ fmt.Sprintf("%#v", timeVal.Format("2006")), fmt.Sprintf("%#v", timeVal.Format("01")), fmt.Sprintf("%#v", timeVal.Format("02")), fmt.Sprintf("%#v", calculateWeek(timeVal)), }, ",", ) } vals = strings.TrimSuffix(vals, ",") tx, err := dbo.Begin() if err != nil { tx.Rollback() log.Printf("[ERR] txBegin: %v\r\n\r\n", err) continue } stmt, err := tx.Prepare(fmt.Sprintf("INSERT INTO %s.%s (%s) VALUES (%s);", cfg.DBConfig.Database, cfg.DBConfig.DestinationTable, cols, vals, )) if err != nil { tx.Rollback() log.Printf("[ERR] txPrepare_Insert: %v\r\n\r\n", err) continue } defer stmt.Close() _, err = stmt.Exec() if err != nil { tx.Rollback() log.Printf("[ERR] txExecute_Insert: %v\r\n\r\n", err) continue } log.Printf("[MSG] Backed Up: %s=%s\r\n", cfg.Settings.PrimaryKey, d[cfg.Settings.PrimaryKey]) if cfg.Settings.DeleteSource { stmt, err = tx.Prepare(fmt.Sprintf("DELETE FROM %s.%s WHERE %s=%s;", cfg.DBConfig.Database, cfg.DBConfig.SourceTable, cfg.Settings.PrimaryKey, fmt.Sprintf("%#v", d[cfg.Settings.PrimaryKey]), )) if err != nil { tx.Rollback() log.Printf("[ERR] txPrepare_Delete: %v\r\n\r\n", err) continue } defer stmt.Close() _, err = stmt.Exec() if err != nil { tx.Rollback() log.Printf("[ERR] txExecute_Delete: %v\r\n\r\n", err) continue } log.Printf("[MSG] Deleted Souce: %s=%s\r\n", cfg.Settings.PrimaryKey, d[cfg.Settings.PrimaryKey]) } err = tx.Commit() if err != nil { tx.Rollback() log.Printf("[ERR] txCommit: %v\r\n\r\n", err) continue } log.Printf("[MSG] ********** Transaction Commited.\r\n\r\n") time.Sleep(time.Millisecond * 100) } return nil } func calculateWeek(t time.Time) string { yearDay := t.YearDay() lastYearEndDay := t.AddDate(0, 0, -yearDay) lastYearEndDayWeek := int(lastYearEndDay.Weekday()) firstWeekDays := 7 if lastYearEndDayWeek != 0 { firstWeekDays = 7 - lastYearEndDayWeek } week := 0 if yearDay <= firstWeekDays { week = 1 } else { plusDay := 0 if (yearDay-firstWeekDays)%7 > 0 { plusDay = 1 } week = (yearDay-firstWeekDays)/7 + 1 + plusDay } return fmt.Sprintf("%02d", week) }