132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
dumpTimeLayout = "2006-01-02 15:04:05.000000"
|
|
zeroDumpTime = "0000-00-00 00:00:00"
|
|
)
|
|
|
|
func NewMySQLBackup(db *gorm.DB, directory string, now func() time.Time) *DumpBackup {
|
|
return NewDumpBackup(func() (DumpSource, error) { return openMySQLDumpSource(db) }, directory, now)
|
|
}
|
|
|
|
type mysqlDumpSource struct {
|
|
tx *gorm.DB
|
|
}
|
|
|
|
var _ DumpSource = &mysqlDumpSource{}
|
|
|
|
func openMySQLDumpSource(db *gorm.DB) (*mysqlDumpSource, error) {
|
|
tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelRepeatableRead, ReadOnly: true})
|
|
if tx.Error != nil {
|
|
return nil, tx.Error
|
|
}
|
|
return &mysqlDumpSource{tx: tx}, nil
|
|
}
|
|
|
|
func (s *mysqlDumpSource) Tables() ([]string, error) {
|
|
var tables []string
|
|
err := s.tx.Raw("SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE' ORDER BY table_name").Scan(&tables).Error
|
|
return tables, err
|
|
}
|
|
|
|
func (s *mysqlDumpSource) CreateStatement(table string) (string, error) {
|
|
var name, statement string
|
|
row := s.tx.Raw("SHOW CREATE TABLE " + quoteIdentifier(table)).Row()
|
|
if err := row.Scan(&name, &statement); err != nil {
|
|
return "", err
|
|
}
|
|
return statement, nil
|
|
}
|
|
|
|
func (s *mysqlDumpSource) Rows(table string) (DumpRows, error) {
|
|
rows, err := s.tx.Raw("SELECT * FROM " + quoteIdentifier(table)).Rows()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mysqlDumpRows{rows: rows}, nil
|
|
}
|
|
|
|
func (s *mysqlDumpSource) Close() error {
|
|
return s.tx.Rollback().Error
|
|
}
|
|
|
|
type mysqlDumpRows struct {
|
|
rows *sql.Rows
|
|
}
|
|
|
|
var _ DumpRows = &mysqlDumpRows{}
|
|
|
|
func (r *mysqlDumpRows) Columns() ([]DumpColumn, error) {
|
|
types, err := r.rows.ColumnTypes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
columns := make([]DumpColumn, len(types))
|
|
for index, columnType := range types {
|
|
columns[index] = DumpColumn{Name: columnType.Name(), Type: columnType.DatabaseTypeName()}
|
|
}
|
|
return columns, nil
|
|
}
|
|
|
|
func (r *mysqlDumpRows) Next() bool {
|
|
return r.rows.Next()
|
|
}
|
|
|
|
func (r *mysqlDumpRows) Values() ([][]byte, error) {
|
|
columnCount, err := r.rows.Columns()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scanned := make([]any, len(columnCount))
|
|
targets := make([]any, len(columnCount))
|
|
for index := range scanned {
|
|
targets[index] = &scanned[index]
|
|
}
|
|
if err := r.rows.Scan(targets...); err != nil {
|
|
return nil, err
|
|
}
|
|
values := make([][]byte, len(scanned))
|
|
for index, value := range scanned {
|
|
values[index] = dumpBytes(value)
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
func (r *mysqlDumpRows) Err() error {
|
|
return r.rows.Err()
|
|
}
|
|
|
|
func (r *mysqlDumpRows) Close() error {
|
|
return r.rows.Close()
|
|
}
|
|
|
|
func dumpBytes(value any) []byte {
|
|
switch typed := value.(type) {
|
|
case nil:
|
|
return nil
|
|
case []byte:
|
|
return append([]byte{}, typed...)
|
|
case string:
|
|
return []byte(typed)
|
|
case time.Time:
|
|
return []byte(formatDumpTime(typed))
|
|
default:
|
|
return []byte(fmt.Sprint(typed))
|
|
}
|
|
}
|
|
|
|
func formatDumpTime(value time.Time) string {
|
|
if value.IsZero() {
|
|
return zeroDumpTime
|
|
}
|
|
return value.UTC().Format(dumpTimeLayout)
|
|
}
|