/Godeps/_workspace/src/github.com/mattes/migrate/migrate/migrate.go

https://gitlab.com/jamesclonk-io/moviedb-backend · Go · 317 lines · 257 code · 32 blank · 28 comment · 98 complexity · b9030af84eb17d6e3d4ad6a2bb8fb6f7 MD5 · raw file

  1. // Package migrate is imported by other Go code.
  2. // It is the entry point to all migration functions.
  3. package migrate
  4. import (
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "os/signal"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "github.com/mattes/migrate/driver"
  13. "github.com/mattes/migrate/file"
  14. "github.com/mattes/migrate/migrate/direction"
  15. pipep "github.com/mattes/migrate/pipe"
  16. )
  17. // Up applies all available migrations
  18. func Up(pipe chan interface{}, url, migrationsPath string) {
  19. d, files, version, err := initDriverAndReadMigrationFilesAndGetVersion(url, migrationsPath)
  20. if err != nil {
  21. go pipep.Close(pipe, err)
  22. return
  23. }
  24. applyMigrationFiles, err := files.ToLastFrom(version)
  25. if err != nil {
  26. if err2 := d.Close(); err2 != nil {
  27. pipe <- err2
  28. }
  29. go pipep.Close(pipe, err)
  30. return
  31. }
  32. if len(applyMigrationFiles) > 0 {
  33. for _, f := range applyMigrationFiles {
  34. pipe1 := pipep.New()
  35. go d.Migrate(f, pipe1)
  36. if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok {
  37. break
  38. }
  39. }
  40. if err := d.Close(); err != nil {
  41. pipe <- err
  42. }
  43. go pipep.Close(pipe, nil)
  44. return
  45. } else {
  46. if err := d.Close(); err != nil {
  47. pipe <- err
  48. }
  49. go pipep.Close(pipe, nil)
  50. return
  51. }
  52. }
  53. // UpSync is synchronous version of Up
  54. func UpSync(url, migrationsPath string) (err []error, ok bool) {
  55. pipe := pipep.New()
  56. go Up(pipe, url, migrationsPath)
  57. err = pipep.ReadErrors(pipe)
  58. return err, len(err) == 0
  59. }
  60. // Down rolls back all migrations
  61. func Down(pipe chan interface{}, url, migrationsPath string) {
  62. d, files, version, err := initDriverAndReadMigrationFilesAndGetVersion(url, migrationsPath)
  63. if err != nil {
  64. go pipep.Close(pipe, err)
  65. return
  66. }
  67. applyMigrationFiles, err := files.ToFirstFrom(version)
  68. if err != nil {
  69. if err2 := d.Close(); err2 != nil {
  70. pipe <- err2
  71. }
  72. go pipep.Close(pipe, err)
  73. return
  74. }
  75. if len(applyMigrationFiles) > 0 {
  76. for _, f := range applyMigrationFiles {
  77. pipe1 := pipep.New()
  78. go d.Migrate(f, pipe1)
  79. if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok {
  80. break
  81. }
  82. }
  83. if err2 := d.Close(); err2 != nil {
  84. pipe <- err2
  85. }
  86. go pipep.Close(pipe, nil)
  87. return
  88. } else {
  89. if err2 := d.Close(); err2 != nil {
  90. pipe <- err2
  91. }
  92. go pipep.Close(pipe, nil)
  93. return
  94. }
  95. }
  96. // DownSync is synchronous version of Down
  97. func DownSync(url, migrationsPath string) (err []error, ok bool) {
  98. pipe := pipep.New()
  99. go Down(pipe, url, migrationsPath)
  100. err = pipep.ReadErrors(pipe)
  101. return err, len(err) == 0
  102. }
  103. // Redo rolls back the most recently applied migration, then runs it again.
  104. func Redo(pipe chan interface{}, url, migrationsPath string) {
  105. pipe1 := pipep.New()
  106. go Migrate(pipe1, url, migrationsPath, -1)
  107. if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok {
  108. go pipep.Close(pipe, nil)
  109. return
  110. } else {
  111. go Migrate(pipe, url, migrationsPath, +1)
  112. }
  113. }
  114. // RedoSync is synchronous version of Redo
  115. func RedoSync(url, migrationsPath string) (err []error, ok bool) {
  116. pipe := pipep.New()
  117. go Redo(pipe, url, migrationsPath)
  118. err = pipep.ReadErrors(pipe)
  119. return err, len(err) == 0
  120. }
  121. // Reset runs the down and up migration function
  122. func Reset(pipe chan interface{}, url, migrationsPath string) {
  123. pipe1 := pipep.New()
  124. go Down(pipe1, url, migrationsPath)
  125. if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok {
  126. go pipep.Close(pipe, nil)
  127. return
  128. } else {
  129. go Up(pipe, url, migrationsPath)
  130. }
  131. }
  132. // ResetSync is synchronous version of Reset
  133. func ResetSync(url, migrationsPath string) (err []error, ok bool) {
  134. pipe := pipep.New()
  135. go Reset(pipe, url, migrationsPath)
  136. err = pipep.ReadErrors(pipe)
  137. return err, len(err) == 0
  138. }
  139. // Migrate applies relative +n/-n migrations
  140. func Migrate(pipe chan interface{}, url, migrationsPath string, relativeN int) {
  141. d, files, version, err := initDriverAndReadMigrationFilesAndGetVersion(url, migrationsPath)
  142. if err != nil {
  143. go pipep.Close(pipe, err)
  144. return
  145. }
  146. applyMigrationFiles, err := files.From(version, relativeN)
  147. if err != nil {
  148. if err2 := d.Close(); err2 != nil {
  149. pipe <- err2
  150. }
  151. go pipep.Close(pipe, err)
  152. return
  153. }
  154. if len(applyMigrationFiles) > 0 && relativeN != 0 {
  155. for _, f := range applyMigrationFiles {
  156. pipe1 := pipep.New()
  157. go d.Migrate(f, pipe1)
  158. if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok {
  159. break
  160. }
  161. }
  162. if err2 := d.Close(); err2 != nil {
  163. pipe <- err2
  164. }
  165. go pipep.Close(pipe, nil)
  166. return
  167. }
  168. if err2 := d.Close(); err2 != nil {
  169. pipe <- err2
  170. }
  171. go pipep.Close(pipe, nil)
  172. return
  173. }
  174. // MigrateSync is synchronous version of Migrate
  175. func MigrateSync(url, migrationsPath string, relativeN int) (err []error, ok bool) {
  176. pipe := pipep.New()
  177. go Migrate(pipe, url, migrationsPath, relativeN)
  178. err = pipep.ReadErrors(pipe)
  179. return err, len(err) == 0
  180. }
  181. // Version returns the current migration version
  182. func Version(url, migrationsPath string) (version uint64, err error) {
  183. d, err := driver.New(url)
  184. if err != nil {
  185. return 0, err
  186. }
  187. return d.Version()
  188. }
  189. // Create creates new migration files on disk
  190. func Create(url, migrationsPath, name string) (*file.MigrationFile, error) {
  191. d, err := driver.New(url)
  192. if err != nil {
  193. return nil, err
  194. }
  195. files, err := file.ReadMigrationFiles(migrationsPath, file.FilenameRegex(d.FilenameExtension()))
  196. if err != nil {
  197. return nil, err
  198. }
  199. version := uint64(0)
  200. if len(files) > 0 {
  201. lastFile := files[len(files)-1]
  202. version = lastFile.Version
  203. }
  204. version += 1
  205. versionStr := strconv.FormatUint(version, 10)
  206. length := 4 // TODO(mattes) check existing files and try to guess length
  207. if len(versionStr)%length != 0 {
  208. versionStr = strings.Repeat("0", length-len(versionStr)%length) + versionStr
  209. }
  210. filenamef := "%s_%s.%s.%s"
  211. name = strings.Replace(name, " ", "_", -1)
  212. mfile := &file.MigrationFile{
  213. Version: version,
  214. UpFile: &file.File{
  215. Path: migrationsPath,
  216. FileName: fmt.Sprintf(filenamef, versionStr, name, "up", d.FilenameExtension()),
  217. Name: name,
  218. Content: []byte(""),
  219. Direction: direction.Up,
  220. },
  221. DownFile: &file.File{
  222. Path: migrationsPath,
  223. FileName: fmt.Sprintf(filenamef, versionStr, name, "down", d.FilenameExtension()),
  224. Name: name,
  225. Content: []byte(""),
  226. Direction: direction.Down,
  227. },
  228. }
  229. if err := ioutil.WriteFile(path.Join(mfile.UpFile.Path, mfile.UpFile.FileName), mfile.UpFile.Content, 0644); err != nil {
  230. return nil, err
  231. }
  232. if err := ioutil.WriteFile(path.Join(mfile.DownFile.Path, mfile.DownFile.FileName), mfile.DownFile.Content, 0644); err != nil {
  233. return nil, err
  234. }
  235. return mfile, nil
  236. }
  237. // initDriverAndReadMigrationFilesAndGetVersion is a small helper
  238. // function that is common to most of the migration funcs
  239. func initDriverAndReadMigrationFilesAndGetVersion(url, migrationsPath string) (driver.Driver, *file.MigrationFiles, uint64, error) {
  240. d, err := driver.New(url)
  241. if err != nil {
  242. return nil, nil, 0, err
  243. }
  244. files, err := file.ReadMigrationFiles(migrationsPath, file.FilenameRegex(d.FilenameExtension()))
  245. if err != nil {
  246. d.Close() // TODO what happens with errors from this func?
  247. return nil, nil, 0, err
  248. }
  249. version, err := d.Version()
  250. if err != nil {
  251. d.Close() // TODO what happens with errors from this func?
  252. return nil, nil, 0, err
  253. }
  254. return d, &files, version, nil
  255. }
  256. // NewPipe is a convenience function for pipe.New().
  257. // This is helpful if the user just wants to import this package and nothing else.
  258. func NewPipe() chan interface{} {
  259. return pipep.New()
  260. }
  261. // interrupts is an internal variable that holds the state of
  262. // interrupt handling
  263. var interrupts = true
  264. // Graceful enables interrupts checking. Once the first ^C is received
  265. // it will finish the currently running migration and abort execution
  266. // of the next migration. If ^C is received twice, it will stop
  267. // execution immediately.
  268. func Graceful() {
  269. interrupts = true
  270. }
  271. // NonGraceful disables interrupts checking. The first received ^C will
  272. // stop execution immediately.
  273. func NonGraceful() {
  274. interrupts = false
  275. }
  276. // interrupts returns a signal channel if interrupts checking is
  277. // enabled. nil otherwise.
  278. func handleInterrupts() chan os.Signal {
  279. if interrupts {
  280. c := make(chan os.Signal, 1)
  281. signal.Notify(c, os.Interrupt)
  282. return c
  283. }
  284. return nil
  285. }