commit c6a110356203788a5de48cff76ee6e9f8c953a1e Author: r0n1n7an Date: Wed Aug 28 16:16:19 2024 +0800 first commit diff --git a/BkupDB.exe b/BkupDB.exe new file mode 100644 index 0000000..4269438 Binary files /dev/null and b/BkupDB.exe differ diff --git a/BkupDB.go b/BkupDB.go new file mode 100644 index 0000000..5378275 --- /dev/null +++ b/BkupDB.go @@ -0,0 +1,318 @@ +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) +} diff --git a/BkupDB.yml b/BkupDB.yml new file mode 100644 index 0000000..dc01c0c --- /dev/null +++ b/BkupDB.yml @@ -0,0 +1,21 @@ +Settings: + Condition: 'TIMESTAMPDIFF(HOUR,last_change,NOW()) > 72' + PrimaryKey: 'seqid' + DeleteSource: true +DBConfig: + Username: apisvc + Password: wcqte + Server: 127.0.0.1 + Port: 3306 + Database: ewsv3_f715 + SourceTable: uutinfo + DestinationTable: uutinfobkup + SourceColumns: [seqid,usn,mac,ipaddr,relay,item,status,message,first_ack,last_ack,last_change,partno,mo,sku,line,stage] + DestinationColumns: [usn,mac,ipaddr,relay,item,status,message,first_ack,last_ack,last_change,partno,mo,sku,line,stage] +FormatColumns: + Enablement: true + Reference: 'last_change' + Year: 'syear' + Month: 'smonth' + Day: 'sday' + Week: 'sweek' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..aaa3747 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module BkupDB + +go 1.20 + +require ( + github.com/go-sql-driver/mysql v1.7.1 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2af5d20 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=