migration.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Copyright (C) 2020 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "database/sql"
  9. "database/sql/driver"
  10. "encoding/json"
  11. "errors"
  12. "log"
  13. "strings"
  14. "github.com/lib/pq"
  15. "github.com/syncthing/syncthing/lib/ur/contract"
  16. )
  17. func migrate(db *sql.DB) error {
  18. var count uint64
  19. log.Println("Checking old table row count, this might take a while...")
  20. if err := db.QueryRow(`SELECT COUNT(1) FROM Reports`).Scan(&count); err != nil || count == 0 {
  21. // err != nil most likely means table does not exist.
  22. return nil
  23. }
  24. log.Printf("Found %d records, will perform migration.", count)
  25. tx, err := db.Begin()
  26. if err != nil {
  27. log.Println("sql:", err)
  28. return err
  29. }
  30. defer tx.Rollback()
  31. // These must be lower case, because we don't quote them when creating, so postgres creates them lower case.
  32. // Yet pg.CopyIn quotes them, which makes them case sensitive.
  33. stmt, err := tx.Prepare(pq.CopyIn("reportsjson", "received", "report"))
  34. if err != nil {
  35. log.Println("sql:", err)
  36. return err
  37. }
  38. // Custom types used in the old struct.
  39. var rep contract.Report
  40. var rescanIntvs pq.Int64Array
  41. var fsWatcherDelay pq.Int64Array
  42. pullOrder := make(IntMap)
  43. fileSystemType := make(IntMap)
  44. themes := make(IntMap)
  45. transportStats := make(IntMap)
  46. rows, err := db.Query(`SELECT ` + strings.Join(rep.FieldNames(), ", ") + `, FolderFsWatcherDelays, RescanIntvs, FolderPullOrder, FolderFilesystemType, GUITheme, Transport FROM Reports`)
  47. if err != nil {
  48. log.Println("sql:", err)
  49. return err
  50. }
  51. defer rows.Close()
  52. var done uint64
  53. pct := count / 100
  54. for rows.Next() {
  55. err := rows.Scan(append(rep.FieldPointers(), &fsWatcherDelay, &rescanIntvs, &pullOrder, &fileSystemType, &themes, &transportStats)...)
  56. if err != nil {
  57. log.Println("sql scan:", err)
  58. return err
  59. }
  60. // Patch up parts that used to use custom types
  61. rep.RescanIntvs = make([]int, len(rescanIntvs))
  62. for i := range rescanIntvs {
  63. rep.RescanIntvs[i] = int(rescanIntvs[i])
  64. }
  65. rep.FolderUsesV3.FsWatcherDelays = make([]int, len(fsWatcherDelay))
  66. for i := range fsWatcherDelay {
  67. rep.FolderUsesV3.FsWatcherDelays[i] = int(fsWatcherDelay[i])
  68. }
  69. rep.FolderUsesV3.PullOrder = pullOrder
  70. rep.FolderUsesV3.FilesystemType = fileSystemType
  71. rep.GUIStats.Theme = themes
  72. rep.TransportStats = transportStats
  73. _, err = stmt.Exec(rep.Received, rep)
  74. if err != nil {
  75. log.Println("sql insert:", err)
  76. return err
  77. }
  78. done++
  79. if done%pct == 0 {
  80. log.Printf("Migration progress %d/%d (%d%%)", done, count, (100*done)/count)
  81. }
  82. }
  83. // Tell the driver bulk copy is finished
  84. _, err = stmt.Exec()
  85. if err != nil {
  86. log.Println("sql stmt exec:", err)
  87. return err
  88. }
  89. err = stmt.Close()
  90. if err != nil {
  91. log.Println("sql stmt close:", err)
  92. return err
  93. }
  94. _, err = tx.Exec("DROP TABLE Reports")
  95. if err != nil {
  96. log.Println("sql drop:", err)
  97. return err
  98. }
  99. err = tx.Commit()
  100. if err != nil {
  101. log.Println("sql commit:", err)
  102. return err
  103. }
  104. return nil
  105. }
  106. type IntMap map[string]int
  107. func (p IntMap) Value() (driver.Value, error) {
  108. return json.Marshal(p)
  109. }
  110. func (p *IntMap) Scan(src interface{}) error {
  111. source, ok := src.([]byte)
  112. if !ok {
  113. return errors.New("Type assertion .([]byte) failed.")
  114. }
  115. var i map[string]int
  116. err := json.Unmarshal(source, &i)
  117. if err != nil {
  118. return err
  119. }
  120. *p = i
  121. return nil
  122. }