You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dump.go 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package testfixtures
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "unicode/utf8"
  8. "gopkg.in/yaml.v2"
  9. )
  10. // Dumper is resposible for dumping fixtures from the database into a
  11. // directory.
  12. type Dumper struct {
  13. db *sql.DB
  14. helper helper
  15. dir string
  16. tables []string
  17. }
  18. // NewDumper creates a new dumper with the given options.
  19. //
  20. // The "DumpDatabase", "DumpDialect" and "DumpDirectory" options are required.
  21. func NewDumper(options ...func(*Dumper) error) (*Dumper, error) {
  22. d := &Dumper{}
  23. for _, option := range options {
  24. if err := option(d); err != nil {
  25. return nil, err
  26. }
  27. }
  28. return d, nil
  29. }
  30. // DumpDatabase sets the database to be dumped.
  31. func DumpDatabase(db *sql.DB) func(*Dumper) error {
  32. return func(d *Dumper) error {
  33. d.db = db
  34. return nil
  35. }
  36. }
  37. // DumpDialect informs Loader about which database dialect you're using.
  38. //
  39. // Possible options are "postgresql", "timescaledb", "mysql", "mariadb",
  40. // "sqlite" and "sqlserver".
  41. func DumpDialect(dialect string) func(*Dumper) error {
  42. return func(d *Dumper) error {
  43. h, err := helperForDialect(dialect)
  44. if err != nil {
  45. return err
  46. }
  47. d.helper = h
  48. return nil
  49. }
  50. }
  51. // DumpDirectory sets the directory where the fixtures files will be created.
  52. func DumpDirectory(dir string) func(*Dumper) error {
  53. return func(d *Dumper) error {
  54. d.dir = dir
  55. return nil
  56. }
  57. }
  58. // DumpTables allows you to choose which tables you want to dump.
  59. //
  60. // If not informed, Dumper will dump all tables by default.
  61. func DumpTables(tables ...string) func(*Dumper) error {
  62. return func(d *Dumper) error {
  63. d.tables = tables
  64. return nil
  65. }
  66. }
  67. // Dump dumps the databases as YAML fixtures.
  68. func (d *Dumper) Dump() error {
  69. tables := d.tables
  70. if len(tables) == 0 {
  71. var err error
  72. tables, err = d.helper.tableNames(d.db)
  73. if err != nil {
  74. return err
  75. }
  76. }
  77. for _, table := range tables {
  78. if err := d.dumpTable(table); err != nil {
  79. return err
  80. }
  81. }
  82. return nil
  83. }
  84. func (d *Dumper) dumpTable(table string) error {
  85. query := fmt.Sprintf("SELECT * FROM %s", d.helper.quoteKeyword(table))
  86. stmt, err := d.db.Prepare(query)
  87. if err != nil {
  88. return err
  89. }
  90. defer stmt.Close()
  91. rows, err := stmt.Query()
  92. if err != nil {
  93. return err
  94. }
  95. defer rows.Close()
  96. columns, err := rows.Columns()
  97. if err != nil {
  98. return err
  99. }
  100. fixtures := make([]yaml.MapSlice, 0, 10)
  101. for rows.Next() {
  102. entries := make([]interface{}, len(columns))
  103. entryPtrs := make([]interface{}, len(entries))
  104. for i := range entries {
  105. entryPtrs[i] = &entries[i]
  106. }
  107. if err := rows.Scan(entryPtrs...); err != nil {
  108. return err
  109. }
  110. entryMap := make([]yaml.MapItem, len(entries))
  111. for i, column := range columns {
  112. entryMap[i] = yaml.MapItem{
  113. Key: column,
  114. Value: convertValue(entries[i]),
  115. }
  116. }
  117. fixtures = append(fixtures, entryMap)
  118. }
  119. if err = rows.Err(); err != nil {
  120. return err
  121. }
  122. filePath := filepath.Join(d.dir, table+".yml")
  123. f, err := os.Create(filePath)
  124. if err != nil {
  125. return err
  126. }
  127. defer f.Close()
  128. data, err := yaml.Marshal(fixtures)
  129. if err != nil {
  130. return err
  131. }
  132. _, err = f.Write(data)
  133. return err
  134. }
  135. func convertValue(value interface{}) interface{} {
  136. switch v := value.(type) {
  137. case []byte:
  138. if utf8.Valid(v) {
  139. return string(v)
  140. }
  141. }
  142. return value
  143. }