-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
James Stewart
committed
Apr 25, 2016
1 parent
7c4b004
commit 7ddc394
Showing
4 changed files
with
326 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# go-sqldump | ||
Create SQL dumps in Go without external dependencies | ||
# Go MYSQL Dump | ||
Create MYSQL dumps in Go without the `mysqldump` CLI as a dependancy. | ||
|
||
[Documentation](https://godoc.org/github.com/JamesStewy/go-mysqldump). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
Create MYSQL dumps in Go without the 'mysqldump' CLI as a dependancy. | ||
Example | ||
This example uses the mymysql driver (example 7 https://github.com/ziutek/mymysql) to connect to a mysql instance. | ||
package main | ||
import ( | ||
"database/sql" | ||
"fmt" | ||
"github.com/JamesStewy/go-mysqldump" | ||
"github.com/ziutek/mymysql/godrv" | ||
"time" | ||
) | ||
func main() { | ||
// Register the mymysql driver | ||
godrv.Register("SET NAMES utf8") | ||
// Open connection to database | ||
db, err := sql.Open("mymysql", "tcp:host:port*database/user/password") | ||
if err != nil { | ||
fmt.Println("Error opening databse:", err) | ||
return | ||
} | ||
// Register database with mysqldump | ||
dumper, err := mysqldump.Register(db, "dumps", time.ANSIC) | ||
if err != nil { | ||
fmt.Println("Error registering databse:", err) | ||
return | ||
} | ||
// Dump database to file | ||
err = dumper.Dump() | ||
if err != nil { | ||
fmt.Println("Error dumping:", err) | ||
return | ||
} | ||
// Close dumper and connected database | ||
dumper.Close() | ||
} | ||
*/ | ||
package mysqldump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package mysqldump | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"os" | ||
"path" | ||
"strings" | ||
"text/template" | ||
"time" | ||
) | ||
|
||
type table struct { | ||
Name string | ||
SQL string | ||
Values string | ||
} | ||
|
||
type dump struct { | ||
DumpVersion string | ||
ServerVersion string | ||
Tables []*table | ||
CompleteTime string | ||
} | ||
|
||
const version = "0.1.0" | ||
|
||
const tmpl = `-- Go SQL Dump {{ .DumpVersion }} | ||
-- | ||
-- ------------------------------------------------------ | ||
-- Server version {{ .ServerVersion }} | ||
{{range .Tables}} | ||
-- | ||
-- Table structure for table {{ .Name }} | ||
-- | ||
DROP TABLE IF EXISTS {{ .Name }}; | ||
{{ .SQL }}; | ||
{{ if .Values }} | ||
-- | ||
-- Dumping data for table {{ .Name }} | ||
-- | ||
LOCK TABLES {{ .Name }} WRITE; | ||
INSERT INTO {{ .Name }} VALUES {{ .Values }}; | ||
UNLOCK TABLES; | ||
{{end}}{{ end }} | ||
-- Dump completed on {{ .CompleteTime }} | ||
` | ||
|
||
// Creates a MYSQL Dump based on the options supplied through the dumper. | ||
func (d *Dumper) Dump() error { | ||
name := time.Now().Format(d.format) | ||
p := path.Join(d.dir, name+".sql") | ||
|
||
// Check dump directory | ||
if e, _ := exists(p); e { | ||
return errors.New("Dump '" + name + "' already exists.") | ||
} | ||
|
||
// Create .sql file | ||
f, err := os.Create(p) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
data := dump{ | ||
DumpVersion: version, | ||
Tables: make([]*table, 0), | ||
} | ||
|
||
// Get server version | ||
if data.ServerVersion, err = getServerVersion(d.db); err != nil { | ||
return err | ||
} | ||
|
||
// Get tables | ||
tables, err := getTables(d.db) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Get sql for each table | ||
for _, name := range tables { | ||
if t, err := createTable(d.db, name); err == nil { | ||
data.Tables = append(data.Tables, t) | ||
} else { | ||
return err | ||
} | ||
} | ||
|
||
// Set complete time | ||
data.CompleteTime = time.Now().String() | ||
|
||
// Write dump to file | ||
t, err := template.New("mysqldump").Parse(tmpl) | ||
if err != nil { | ||
return err | ||
} | ||
if err = t.Execute(f, data); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getTables(db *sql.DB) ([]string, error) { | ||
tables := make([]string, 0) | ||
|
||
// Get table list | ||
rows, err := db.Query("SHOW TABLES") | ||
if err != nil { | ||
return tables, err | ||
} | ||
defer rows.Close() | ||
|
||
// Read result | ||
for rows.Next() { | ||
var table string | ||
if err := rows.Scan(&table); err != nil { | ||
return tables, err | ||
} | ||
tables = append(tables, table) | ||
} | ||
return tables, rows.Err() | ||
} | ||
|
||
func getServerVersion(db *sql.DB) (string, error) { | ||
var server_version string | ||
if err := db.QueryRow("SELECT version()").Scan(&server_version); err != nil { | ||
return "", err | ||
} | ||
return server_version, nil | ||
} | ||
|
||
func createTable(db *sql.DB, name string) (*table, error) { | ||
var err error | ||
t := &table{Name: name} | ||
|
||
if t.SQL, err = createTableSQL(db, name); err != nil { | ||
return nil, err | ||
} | ||
|
||
if t.Values, err = createTableValues(db, name); err != nil { | ||
return nil, err | ||
} | ||
|
||
return t, nil | ||
} | ||
|
||
func createTableSQL(db *sql.DB, name string) (string, error) { | ||
// Get table creation SQL | ||
var table_return string | ||
var table_sql string | ||
err := db.QueryRow("SHOW CREATE TABLE "+name).Scan(&table_return, &table_sql) | ||
if err != nil { | ||
return "", err | ||
} | ||
if table_return != name { | ||
return "", errors.New("Returned table is not the same as requested table") | ||
} | ||
|
||
return table_sql, nil | ||
} | ||
|
||
func createTableValues(db *sql.DB, name string) (string, error) { | ||
// Get Data | ||
rows, err := db.Query("SELECT * FROM " + name) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer rows.Close() | ||
|
||
// Get columns | ||
columns, err := rows.Columns() | ||
if err != nil { | ||
return "", err | ||
} | ||
if len(columns) == 0 { | ||
return "", errors.New("No columns in table " + name + ".") | ||
} | ||
|
||
// Read data | ||
data_text := make([]string, 0) | ||
for rows.Next() { | ||
// Init temp data storage | ||
data := make([]string, len(columns)) | ||
ptrs := make([]interface{}, len(columns)) | ||
for i, _ := range data { | ||
ptrs[i] = &data[i] | ||
} | ||
|
||
// Read data | ||
if err := rows.Scan(ptrs...); err != nil { | ||
return "", err | ||
} | ||
data_text = append(data_text, "('"+strings.Join(data, "','")+"')") | ||
} | ||
|
||
return strings.Join(data_text, ","), rows.Err() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package mysqldump | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"os" | ||
) | ||
|
||
// Dumper represents a database. | ||
type Dumper struct { | ||
db *sql.DB | ||
format string | ||
dir string | ||
} | ||
|
||
/* | ||
Creates a new dumper. | ||
db: Database that will be dumped (https://golang.org/pkg/database/sql/#DB). | ||
dir: Path to the directory where the dumps will be stored. | ||
format: Format to be used to name each dump file. Uses time.Time.Format (https://golang.org/pkg/time/#Time.Format). format appended with '.sql'. | ||
*/ | ||
func Register(db *sql.DB, dir, format string) (*Dumper, error) { | ||
if !isDir(dir) { | ||
return nil, errors.New("Invalid directory") | ||
} | ||
|
||
return &Dumper{ | ||
db: db, | ||
format: format, | ||
dir: dir, | ||
}, nil | ||
} | ||
|
||
// Closes the dumper. | ||
// Will also close the database the dumper is connected to. | ||
// | ||
// Not required. | ||
func (d *Dumper) Close() error { | ||
defer func() { | ||
d.db = nil | ||
}() | ||
return d.db.Close() | ||
} | ||
|
||
func exists(p string) (bool, os.FileInfo) { | ||
f, err := os.Open(p) | ||
if err != nil { | ||
return false, nil | ||
} | ||
defer f.Close() | ||
fi, err := f.Stat() | ||
if err != nil { | ||
return false, nil | ||
} | ||
return true, fi | ||
} | ||
|
||
func isFile(p string) bool { | ||
if e, fi := exists(p); e { | ||
return fi.Mode().IsRegular() | ||
} | ||
return false | ||
} | ||
|
||
func isDir(p string) bool { | ||
if e, fi := exists(p); e { | ||
return fi.Mode().IsDir() | ||
} | ||
return false | ||
} |