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.

migrations.go 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package migrations
  6. import (
  7. "context"
  8. "errors"
  9. "fmt"
  10. "os"
  11. "reflect"
  12. "regexp"
  13. "strings"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "xorm.io/xorm"
  17. "xorm.io/xorm/names"
  18. "xorm.io/xorm/schemas"
  19. )
  20. const minDBVersion = 70 // Gitea 1.5.3
  21. // Migration describes on migration from lower version to high version
  22. type Migration interface {
  23. Description() string
  24. Migrate(*xorm.Engine) error
  25. }
  26. type migration struct {
  27. description string
  28. migrate func(*xorm.Engine) error
  29. }
  30. // NewMigration creates a new migration
  31. func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
  32. return &migration{desc, fn}
  33. }
  34. // Description returns the migration's description
  35. func (m *migration) Description() string {
  36. return m.description
  37. }
  38. // Migrate executes the migration
  39. func (m *migration) Migrate(x *xorm.Engine) error {
  40. return m.migrate(x)
  41. }
  42. // Version describes the version table. Should have only one row with id==1
  43. type Version struct {
  44. ID int64 `xorm:"pk autoincr"`
  45. Version int64
  46. }
  47. // This is a sequence of migrations. Add new migrations to the bottom of the list.
  48. // If you want to "retire" a migration, remove it from the top of the list and
  49. // update minDBVersion accordingly
  50. var migrations = []Migration{
  51. // Gitea 1.5.0 ends at v69
  52. // v70 -> v71
  53. NewMigration("add issue_dependencies", addIssueDependencies),
  54. // v71 -> v72
  55. NewMigration("protect each scratch token", addScratchHash),
  56. // v72 -> v73
  57. NewMigration("add review", addReview),
  58. // Gitea 1.6.0 ends at v73
  59. // v73 -> v74
  60. NewMigration("add must_change_password column for users table", addMustChangePassword),
  61. // v74 -> v75
  62. NewMigration("add approval whitelists to protected branches", addApprovalWhitelistsToProtectedBranches),
  63. // v75 -> v76
  64. NewMigration("clear nonused data which not deleted when user was deleted", clearNonusedData),
  65. // Gitea 1.7.0 ends at v76
  66. // v76 -> v77
  67. NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMerge),
  68. // v77 -> v78
  69. NewMigration("add theme to users", addUserDefaultTheme),
  70. // v78 -> v79
  71. NewMigration("rename repo is_bare to repo is_empty", renameRepoIsBareToIsEmpty),
  72. // v79 -> v80
  73. NewMigration("add can close issues via commit in any branch", addCanCloseIssuesViaCommitInAnyBranch),
  74. // v80 -> v81
  75. NewMigration("add is locked to issues", addIsLockedToIssues),
  76. // v81 -> v82
  77. NewMigration("update U2F counter type", changeU2FCounterType),
  78. // Gitea 1.8.0 ends at v82
  79. // v82 -> v83
  80. NewMigration("hot fix for wrong release sha1 on release table", fixReleaseSha1OnReleaseTable),
  81. // v83 -> v84
  82. NewMigration("add uploader id for table attachment", addUploaderIDForAttachment),
  83. // v84 -> v85
  84. NewMigration("add table to store original imported gpg keys", addGPGKeyImport),
  85. // v85 -> v86
  86. NewMigration("hash application token", hashAppToken),
  87. // v86 -> v87
  88. NewMigration("add http method to webhook", addHTTPMethodToWebhook),
  89. // v87 -> v88
  90. NewMigration("add avatar field to repository", addAvatarFieldToRepository),
  91. // Gitea 1.9.0 ends at v88
  92. // v88 -> v89
  93. NewMigration("add commit status context field to commit_status", addCommitStatusContext),
  94. // v89 -> v90
  95. NewMigration("add original author/url migration info to issues, comments, and repo ", addOriginalMigrationInfo),
  96. // v90 -> v91
  97. NewMigration("change length of some repository columns", changeSomeColumnsLengthOfRepo),
  98. // v91 -> v92
  99. NewMigration("add index on owner_id of repository and type, review_id of comment", addIndexOnRepositoryAndComment),
  100. // v92 -> v93
  101. NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus),
  102. // v93 -> v94
  103. NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser),
  104. // v94 -> v95
  105. NewMigration("add enable_status_check, status_check_contexts to protected_branch", addStatusCheckColumnsForProtectedBranches),
  106. // v95 -> v96
  107. NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns),
  108. // v96 -> v97
  109. NewMigration("delete orphaned attachments", deleteOrphanedAttachments),
  110. // v97 -> v98
  111. NewMigration("add repo_admin_change_team_access to user", addRepoAdminChangeTeamAccessColumnForUser),
  112. // v98 -> v99
  113. NewMigration("add original author name and id on migrated release", addOriginalAuthorOnMigratedReleases),
  114. // v99 -> v100
  115. NewMigration("add task table and status column for repository table", addTaskTable),
  116. // v100 -> v101
  117. NewMigration("update migration repositories' service type", updateMigrationServiceTypes),
  118. // v101 -> v102
  119. NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser),
  120. // Gitea 1.10.0 ends at v102
  121. // v102 -> v103
  122. NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest),
  123. // v103 -> v104
  124. NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
  125. // v104 -> v105
  126. NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
  127. // v105 -> v106
  128. NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
  129. // v106 -> v107
  130. NewMigration("add column `mode` to table watch", addModeColumnToWatch),
  131. // v107 -> v108
  132. NewMigration("Add template options to repository", addTemplateToRepo),
  133. // v108 -> v109
  134. NewMigration("Add comment_id on table notification", addCommentIDOnNotification),
  135. // v109 -> v110
  136. NewMigration("add can_create_org_repo to team", addCanCreateOrgRepoColumnForTeam),
  137. // v110 -> v111
  138. NewMigration("change review content type to text", changeReviewContentToText),
  139. // v111 -> v112
  140. NewMigration("update branch protection for can push and whitelist enable", addBranchProtectionCanPushAndEnableWhitelist),
  141. // v112 -> v113
  142. NewMigration("remove release attachments which repository deleted", removeAttachmentMissedRepo),
  143. // v113 -> v114
  144. NewMigration("new feature: change target branch of pull requests", featureChangeTargetBranch),
  145. // v114 -> v115
  146. NewMigration("Remove authentication credentials from stored URL", sanitizeOriginalURL),
  147. // v115 -> v116
  148. NewMigration("add user_id prefix to existing user avatar name", renameExistingUserAvatarName),
  149. // v116 -> v117
  150. NewMigration("Extend TrackedTimes", extendTrackedTimes),
  151. // Gitea 1.11.0 ends at v117
  152. // v117 -> v118
  153. NewMigration("Add block on rejected reviews branch protection", addBlockOnRejectedReviews),
  154. // v118 -> v119
  155. NewMigration("Add commit id and stale to reviews", addReviewCommitAndStale),
  156. // v119 -> v120
  157. NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType),
  158. // v120 -> v121
  159. NewMigration("Add owner_name on table repository", addOwnerNameOnRepository),
  160. // v121 -> v122
  161. NewMigration("add is_restricted column for users table", addIsRestricted),
  162. // v122 -> v123
  163. NewMigration("Add Require Signed Commits to ProtectedBranch", addRequireSignedCommits),
  164. // v123 -> v124
  165. NewMigration("Add original information for reactions", addReactionOriginals),
  166. // v124 -> v125
  167. NewMigration("Add columns to user and repository", addUserRepoMissingColumns),
  168. // v125 -> v126
  169. NewMigration("Add some columns on review for migration", addReviewMigrateInfo),
  170. // v126 -> v127
  171. NewMigration("Fix topic repository count", fixTopicRepositoryCount),
  172. // v127 -> v128
  173. NewMigration("add repository code language statistics", addLanguageStats),
  174. // v128 -> v129
  175. NewMigration("fix merge base for pull requests", fixMergeBase),
  176. // v129 -> v130
  177. NewMigration("remove dependencies from deleted repositories", purgeUnusedDependencies),
  178. // v130 -> v131
  179. NewMigration("Expand webhooks for more granularity", expandWebhooks),
  180. // v131 -> v132
  181. NewMigration("Add IsSystemWebhook column to webhooks table", addSystemWebhookColumn),
  182. // v132 -> v133
  183. NewMigration("Add Branch Protection Protected Files Column", addBranchProtectionProtectedFilesColumn),
  184. // v133 -> v134
  185. NewMigration("Add EmailHash Table", addEmailHashTable),
  186. // v134 -> v135
  187. NewMigration("Refix merge base for merged pull requests", refixMergeBase),
  188. // v135 -> v136
  189. NewMigration("Add OrgID column to Labels table", addOrgIDLabelColumn),
  190. // v136 -> v137
  191. NewMigration("Add CommitsAhead and CommitsBehind Column to PullRequest Table", addCommitDivergenceToPulls),
  192. // v137 -> v138
  193. NewMigration("Add Branch Protection Block Outdated Branch", addBlockOnOutdatedBranch),
  194. // v138 -> v139
  195. NewMigration("Add ResolveDoerID to Comment table", addResolveDoerIDCommentColumn),
  196. // v139 -> v140
  197. NewMigration("prepend refs/heads/ to issue refs", prependRefsHeadsToIssueRefs),
  198. // Gitea 1.12.0 ends at v140
  199. // v140 -> v141
  200. NewMigration("Save detected language file size to database instead of percent", fixLanguageStatsToSaveSize),
  201. // v141 -> v142
  202. NewMigration("Add KeepActivityPrivate to User table", addKeepActivityPrivateUserColumn),
  203. // v142 -> v143
  204. NewMigration("Ensure Repository.IsArchived is not null", setIsArchivedToFalse),
  205. // v143 -> v144
  206. NewMigration("recalculate Stars number for all user", recalculateStars),
  207. // v144 -> v145
  208. NewMigration("update Matrix Webhook http method to 'PUT'", updateMatrixWebhookHTTPMethod),
  209. // v145 -> v146
  210. NewMigration("Increase Language field to 50 in LanguageStats", increaseLanguageField),
  211. // v146 -> v147
  212. NewMigration("Add projects info to repository table", addProjectsInfo),
  213. // v147 -> v148
  214. NewMigration("create review for 0 review id code comments", createReviewsForCodeComments),
  215. // v148 -> v149
  216. NewMigration("remove issue dependency comments who refer to non existing issues", purgeInvalidDependenciesComments),
  217. // v149 -> v150
  218. NewMigration("Add Created and Updated to Milestone table", addCreatedAndUpdatedToMilestones),
  219. // v150 -> v151
  220. NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic),
  221. // v151 -> v152
  222. NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2),
  223. // v152 -> v153
  224. NewMigration("add TrustModel field to Repository", addTrustModelToRepository),
  225. // v153 > v154
  226. NewMigration("add Team review request support", addTeamReviewRequestSupport),
  227. // v154 > v155
  228. NewMigration("add timestamps to Star, Label, Follow, Watch and Collaboration", addTimeStamps),
  229. // Gitea 1.13.0 ends at v155
  230. // v155 -> v156
  231. NewMigration("add changed_protected_files column for pull_request table", addChangedProtectedFilesPullRequestColumn),
  232. // v156 -> v157
  233. NewMigration("fix publisher ID for tag releases", fixPublisherIDforTagReleases),
  234. // v157 -> v158
  235. NewMigration("ensure repo topics are up-to-date", fixRepoTopics),
  236. // v158 -> v159
  237. NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
  238. // v159 -> v160
  239. NewMigration("update reactions constraint", updateReactionConstraint),
  240. // v160 -> v161
  241. NewMigration("Add block on official review requests branch protection", addBlockOnOfficialReviewRequests),
  242. // v161 -> v162
  243. NewMigration("Convert task type from int to string", convertTaskTypeToString),
  244. // v162 -> v163
  245. NewMigration("Convert webhook task type from int to string", convertWebhookTaskTypeToString),
  246. // v163 -> v164
  247. NewMigration("Convert topic name from 25 to 50", convertTopicNameFrom25To50),
  248. // v164 -> v165
  249. NewMigration("Add scope and nonce columns to oauth2_grant table", addScopeAndNonceColumnsToOAuth2Grant),
  250. // v165 -> v166
  251. NewMigration("Convert hook task type from char(16) to varchar(16) and trim the column", convertHookTaskTypeToVarcharAndTrim),
  252. // v166 -> v167
  253. NewMigration("Where Password is Valid with Empty String delete it", recalculateUserEmptyPWD),
  254. // v167 -> v168
  255. NewMigration("Add user redirect", addUserRedirect),
  256. // v168 -> v169
  257. NewMigration("Recreate user table to fix default values", recreateUserTableToFixDefaultValues),
  258. // v169 -> v170
  259. NewMigration("Update DeleteBranch comments to set the old_ref to the commit_sha", commentTypeDeleteBranchUseOldRef),
  260. // v170 -> v171
  261. NewMigration("Add Dismissed to Review table", addDismissedReviewColumn),
  262. // v171 -> v172
  263. NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard),
  264. // v172 -> v173
  265. NewMigration("Add sessions table for go-chi/session", addSessionTable),
  266. // v173 -> v174
  267. NewMigration("Add time_id column to Comment", addTimeIDCommentColumn),
  268. // v174 -> v175
  269. NewMigration("Create repo transfer table", addRepoTransfer),
  270. // v175 -> v176
  271. NewMigration("Fix Postgres ID Sequences broken by recreate-table", fixPostgresIDSequences),
  272. // v176 -> v177
  273. NewMigration("Remove invalid labels from comments", removeInvalidLabels),
  274. // v177 -> v178
  275. NewMigration("Delete orphaned IssueLabels", deleteOrphanedIssueLabels),
  276. // Gitea 1.14.0 ends at v178
  277. // v178 -> v179
  278. NewMigration("Add LFS columns to Mirror", addLFSMirrorColumns),
  279. // v179 -> v180
  280. NewMigration("Convert avatar url to text", convertAvatarURLToText),
  281. // v180 -> v181
  282. NewMigration("Delete credentials from past migrations", deleteMigrationCredentials),
  283. // v181 -> v182
  284. NewMigration("Always save primary email on email address table", addPrimaryEmail2EmailAddress),
  285. // v182 -> v183
  286. NewMigration("Add issue resource index table", addIssueResourceIndexTable),
  287. // v183 -> v184
  288. NewMigration("Create PushMirror table", createPushMirrorTable),
  289. // v184 -> v185
  290. NewMigration("Rename Task errors to message", renameTaskErrorsToMessage),
  291. // v185 -> v186
  292. NewMigration("Add new table repo_archiver", addRepoArchiver),
  293. // v186 -> v187
  294. NewMigration("Create protected tag table", createProtectedTagTable),
  295. // v187 -> v188
  296. NewMigration("Drop unneeded webhook related columns", dropWebhookColumns),
  297. // v188 -> v189
  298. NewMigration("Add key is verified to gpg key", addKeyIsVerified),
  299. // Gitea 1.15.0 ends at v189
  300. // v189 -> v190
  301. NewMigration("Unwrap ldap.Sources", unwrapLDAPSourceCfg),
  302. // v190 -> v191
  303. NewMigration("Add agit flow pull request support", addAgitFlowPullRequest),
  304. // v191 -> v192
  305. NewMigration("Alter issue/comment table TEXT fields to LONGTEXT", alterIssueAndCommentTextFieldsToLongText),
  306. // v192 -> v193
  307. NewMigration("RecreateIssueResourceIndexTable to have a primary key instead of an unique index", recreateIssueResourceIndexTable),
  308. // v193 -> v194
  309. NewMigration("Add repo id column for attachment table", addRepoIDForAttachment),
  310. // v194 -> v195
  311. NewMigration("Add Branch Protection Unprotected Files Column", addBranchProtectionUnprotectedFilesColumn),
  312. // v195 -> v196
  313. NewMigration("Add table commit_status_index", addTableCommitStatusIndex),
  314. // v196 -> v197
  315. NewMigration("Add Color to ProjectBoard table", addColorColToProjectBoard),
  316. // v197 -> v198
  317. NewMigration("Add renamed_branch table", addRenamedBranchTable),
  318. // v198 -> v199
  319. NewMigration("Add issue content history table", addTableIssueContentHistory),
  320. // v199 -> v200
  321. NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop),
  322. // v200 -> v201
  323. NewMigration("Add table app_state", addTableAppState),
  324. // v201 -> v202
  325. NewMigration("Drop table remote_version (if exists)", dropTableRemoteVersion),
  326. // v202 -> v203
  327. NewMigration("Create key/value table for user settings", createUserSettingsTable),
  328. // v203 -> v204
  329. NewMigration("Add Sorting to ProjectIssue table", addProjectIssueSorting),
  330. // v204 -> v205
  331. NewMigration("Add key is verified to ssh key", addSSHKeyIsVerified),
  332. // v205 -> v206
  333. NewMigration("Migrate to higher varchar on user struct", migrateUserPasswordSalt),
  334. // v206 -> v207
  335. NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
  336. // v207 -> v208
  337. NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
  338. }
  339. // GetCurrentDBVersion returns the current db version
  340. func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
  341. if err := x.Sync(new(Version)); err != nil {
  342. return -1, fmt.Errorf("sync: %v", err)
  343. }
  344. currentVersion := &Version{ID: 1}
  345. has, err := x.Get(currentVersion)
  346. if err != nil {
  347. return -1, fmt.Errorf("get: %v", err)
  348. }
  349. if !has {
  350. return -1, nil
  351. }
  352. return currentVersion.Version, nil
  353. }
  354. // ExpectedVersion returns the expected db version
  355. func ExpectedVersion() int64 {
  356. return int64(minDBVersion + len(migrations))
  357. }
  358. // EnsureUpToDate will check if the db is at the correct version
  359. func EnsureUpToDate(x *xorm.Engine) error {
  360. currentDB, err := GetCurrentDBVersion(x)
  361. if err != nil {
  362. return err
  363. }
  364. if currentDB < 0 {
  365. return fmt.Errorf("Database has not been initialised")
  366. }
  367. if minDBVersion > currentDB {
  368. return fmt.Errorf("DB version %d (<= %d) is too old for auto-migration. Upgrade to Gitea 1.6.4 first then upgrade to this version", currentDB, minDBVersion)
  369. }
  370. expected := ExpectedVersion()
  371. if currentDB != expected {
  372. return fmt.Errorf(`Current database version %d is not equal to the expected version %d. Please run "gitea [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected)
  373. }
  374. return nil
  375. }
  376. // Migrate database to current version
  377. func Migrate(x *xorm.Engine) error {
  378. // Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
  379. x.SetMapper(names.GonicMapper{})
  380. if err := x.Sync(new(Version)); err != nil {
  381. return fmt.Errorf("sync: %v", err)
  382. }
  383. currentVersion := &Version{ID: 1}
  384. has, err := x.Get(currentVersion)
  385. if err != nil {
  386. return fmt.Errorf("get: %v", err)
  387. } else if !has {
  388. // If the version record does not exist we think
  389. // it is a fresh installation and we can skip all migrations.
  390. currentVersion.ID = 0
  391. currentVersion.Version = int64(minDBVersion + len(migrations))
  392. if _, err = x.InsertOne(currentVersion); err != nil {
  393. return fmt.Errorf("insert: %v", err)
  394. }
  395. }
  396. v := currentVersion.Version
  397. if minDBVersion > v {
  398. log.Fatal(`Gitea no longer supports auto-migration from your previously installed version.
  399. Please try upgrading to a lower version first (suggested v1.6.4), then upgrade to this version.`)
  400. return nil
  401. }
  402. // Downgrading Gitea's database version not supported
  403. if int(v-minDBVersion) > len(migrations) {
  404. msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n",
  405. v, minDBVersion+len(migrations), minDBVersion+len(migrations))
  406. fmt.Fprint(os.Stderr, msg)
  407. log.Fatal(msg)
  408. return nil
  409. }
  410. // Migrate
  411. for i, m := range migrations[v-minDBVersion:] {
  412. log.Info("Migration[%d]: %s", v+int64(i), m.Description())
  413. // Reset the mapper between each migration - migrations are not supposed to depend on each other
  414. x.SetMapper(names.GonicMapper{})
  415. if err = m.Migrate(x); err != nil {
  416. return fmt.Errorf("migration[%d]: %s failed: %v", v+int64(i), m.Description(), err)
  417. }
  418. currentVersion.Version = v + int64(i) + 1
  419. if _, err = x.ID(1).Update(currentVersion); err != nil {
  420. return err
  421. }
  422. }
  423. return nil
  424. }
  425. // RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table
  426. // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
  427. func RecreateTables(beans ...interface{}) func(*xorm.Engine) error {
  428. return func(x *xorm.Engine) error {
  429. sess := x.NewSession()
  430. defer sess.Close()
  431. if err := sess.Begin(); err != nil {
  432. return err
  433. }
  434. sess = sess.StoreEngine("InnoDB")
  435. for _, bean := range beans {
  436. log.Info("Recreating Table: %s for Bean: %s", x.TableName(bean), reflect.Indirect(reflect.ValueOf(bean)).Type().Name())
  437. if err := recreateTable(sess, bean); err != nil {
  438. return err
  439. }
  440. }
  441. return sess.Commit()
  442. }
  443. }
  444. // recreateTable will recreate the table using the newly provided bean definition and move all data to that new table
  445. // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
  446. // WARNING: YOU MUST COMMIT THE SESSION AT THE END
  447. func recreateTable(sess *xorm.Session, bean interface{}) error {
  448. // TODO: This will not work if there are foreign keys
  449. tableName := sess.Engine().TableName(bean)
  450. tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName)
  451. // We need to move the old table away and create a new one with the correct columns
  452. // We will need to do this in stages to prevent data loss
  453. //
  454. // First create the temporary table
  455. if err := sess.Table(tempTableName).CreateTable(bean); err != nil {
  456. log.Error("Unable to create table %s. Error: %v", tempTableName, err)
  457. return err
  458. }
  459. if err := sess.Table(tempTableName).CreateUniques(bean); err != nil {
  460. log.Error("Unable to create uniques for table %s. Error: %v", tempTableName, err)
  461. return err
  462. }
  463. if err := sess.Table(tempTableName).CreateIndexes(bean); err != nil {
  464. log.Error("Unable to create indexes for table %s. Error: %v", tempTableName, err)
  465. return err
  466. }
  467. // Work out the column names from the bean - these are the columns to select from the old table and install into the new table
  468. table, err := sess.Engine().TableInfo(bean)
  469. if err != nil {
  470. log.Error("Unable to get table info. Error: %v", err)
  471. return err
  472. }
  473. newTableColumns := table.Columns()
  474. if len(newTableColumns) == 0 {
  475. return fmt.Errorf("no columns in new table")
  476. }
  477. hasID := false
  478. for _, column := range newTableColumns {
  479. hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
  480. }
  481. if hasID && setting.Database.UseMSSQL {
  482. if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil {
  483. log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err)
  484. return err
  485. }
  486. }
  487. sqlStringBuilder := &strings.Builder{}
  488. _, _ = sqlStringBuilder.WriteString("INSERT INTO `")
  489. _, _ = sqlStringBuilder.WriteString(tempTableName)
  490. _, _ = sqlStringBuilder.WriteString("` (`")
  491. _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
  492. _, _ = sqlStringBuilder.WriteString("`")
  493. for _, column := range newTableColumns[1:] {
  494. _, _ = sqlStringBuilder.WriteString(", `")
  495. _, _ = sqlStringBuilder.WriteString(column.Name)
  496. _, _ = sqlStringBuilder.WriteString("`")
  497. }
  498. _, _ = sqlStringBuilder.WriteString(")")
  499. _, _ = sqlStringBuilder.WriteString(" SELECT ")
  500. if newTableColumns[0].Default != "" {
  501. _, _ = sqlStringBuilder.WriteString("COALESCE(`")
  502. _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
  503. _, _ = sqlStringBuilder.WriteString("`, ")
  504. _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default)
  505. _, _ = sqlStringBuilder.WriteString(")")
  506. } else {
  507. _, _ = sqlStringBuilder.WriteString("`")
  508. _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
  509. _, _ = sqlStringBuilder.WriteString("`")
  510. }
  511. for _, column := range newTableColumns[1:] {
  512. if column.Default != "" {
  513. _, _ = sqlStringBuilder.WriteString(", COALESCE(`")
  514. _, _ = sqlStringBuilder.WriteString(column.Name)
  515. _, _ = sqlStringBuilder.WriteString("`, ")
  516. _, _ = sqlStringBuilder.WriteString(column.Default)
  517. _, _ = sqlStringBuilder.WriteString(")")
  518. } else {
  519. _, _ = sqlStringBuilder.WriteString(", `")
  520. _, _ = sqlStringBuilder.WriteString(column.Name)
  521. _, _ = sqlStringBuilder.WriteString("`")
  522. }
  523. }
  524. _, _ = sqlStringBuilder.WriteString(" FROM `")
  525. _, _ = sqlStringBuilder.WriteString(tableName)
  526. _, _ = sqlStringBuilder.WriteString("`")
  527. if _, err := sess.Exec(sqlStringBuilder.String()); err != nil {
  528. log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err)
  529. return err
  530. }
  531. if hasID && setting.Database.UseMSSQL {
  532. if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil {
  533. log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err)
  534. return err
  535. }
  536. }
  537. switch {
  538. case setting.Database.UseSQLite3:
  539. // SQLite will drop all the constraints on the old table
  540. if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
  541. log.Error("Unable to drop old table %s. Error: %v", tableName, err)
  542. return err
  543. }
  544. if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
  545. log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
  546. return err
  547. }
  548. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
  549. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  550. return err
  551. }
  552. if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
  553. log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
  554. return err
  555. }
  556. if err := sess.Table(tableName).CreateUniques(bean); err != nil {
  557. log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
  558. return err
  559. }
  560. case setting.Database.UseMySQL:
  561. // MySQL will drop all the constraints on the old table
  562. if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
  563. log.Error("Unable to drop old table %s. Error: %v", tableName, err)
  564. return err
  565. }
  566. if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
  567. log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
  568. return err
  569. }
  570. // SQLite and MySQL will move all the constraints from the temporary table to the new table
  571. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
  572. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  573. return err
  574. }
  575. if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
  576. log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
  577. return err
  578. }
  579. if err := sess.Table(tableName).CreateUniques(bean); err != nil {
  580. log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
  581. return err
  582. }
  583. case setting.Database.UsePostgreSQL:
  584. var originalSequences []string
  585. type sequenceData struct {
  586. LastValue int `xorm:"'last_value'"`
  587. IsCalled bool `xorm:"'is_called'"`
  588. }
  589. sequenceMap := map[string]sequenceData{}
  590. schema := sess.Engine().Dialect().URI().Schema
  591. sess.Engine().SetSchema("")
  592. if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil {
  593. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  594. return err
  595. }
  596. sess.Engine().SetSchema(schema)
  597. for _, sequence := range originalSequences {
  598. sequenceData := sequenceData{}
  599. if _, err := sess.Table(sequence).Cols("last_value", "is_called").Get(&sequenceData); err != nil {
  600. log.Error("Unable to get last_value and is_called from %s. Error: %v", sequence, err)
  601. return err
  602. }
  603. sequenceMap[sequence] = sequenceData
  604. }
  605. // CASCADE causes postgres to drop all the constraints on the old table
  606. if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
  607. log.Error("Unable to drop old table %s. Error: %v", tableName, err)
  608. return err
  609. }
  610. // CASCADE causes postgres to move all the constraints from the temporary table to the new table
  611. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
  612. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  613. return err
  614. }
  615. var indices []string
  616. sess.Engine().SetSchema("")
  617. if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
  618. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  619. return err
  620. }
  621. sess.Engine().SetSchema(schema)
  622. for _, index := range indices {
  623. newIndexName := strings.Replace(index, "tmp_recreate__", "", 1)
  624. if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil {
  625. log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err)
  626. return err
  627. }
  628. }
  629. var sequences []string
  630. sess.Engine().SetSchema("")
  631. if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil {
  632. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  633. return err
  634. }
  635. sess.Engine().SetSchema(schema)
  636. for _, sequence := range sequences {
  637. newSequenceName := strings.Replace(sequence, "tmp_recreate__", "", 1)
  638. if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
  639. log.Error("Unable to rename %s sequence to %s. Error: %v", sequence, newSequenceName, err)
  640. return err
  641. }
  642. val, ok := sequenceMap[newSequenceName]
  643. if newSequenceName == tableName+"_id_seq" {
  644. if ok && val.LastValue != 0 {
  645. if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
  646. log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
  647. return err
  648. }
  649. } else {
  650. // We're going to try to guess this
  651. if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
  652. log.Error("Unable to reset %s. Error: %v", newSequenceName, err)
  653. return err
  654. }
  655. }
  656. } else if ok {
  657. if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
  658. log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
  659. return err
  660. }
  661. }
  662. }
  663. case setting.Database.UseMSSQL:
  664. // MSSQL will drop all the constraints on the old table
  665. if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
  666. log.Error("Unable to drop old table %s. Error: %v", tableName, err)
  667. return err
  668. }
  669. // MSSQL sp_rename will move all the constraints from the temporary table to the new table
  670. if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil {
  671. log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
  672. return err
  673. }
  674. default:
  675. log.Fatal("Unrecognized DB")
  676. }
  677. return nil
  678. }
  679. // WARNING: YOU MUST COMMIT THE SESSION AT THE END
  680. func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...string) (err error) {
  681. if tableName == "" || len(columnNames) == 0 {
  682. return nil
  683. }
  684. // TODO: This will not work if there are foreign keys
  685. switch {
  686. case setting.Database.UseSQLite3:
  687. // First drop the indexes on the columns
  688. res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
  689. if errIndex != nil {
  690. return errIndex
  691. }
  692. for _, row := range res {
  693. indexName := row["name"]
  694. indexRes, err := sess.Query(fmt.Sprintf("PRAGMA index_info(`%s`)", indexName))
  695. if err != nil {
  696. return err
  697. }
  698. if len(indexRes) != 1 {
  699. continue
  700. }
  701. indexColumn := string(indexRes[0]["name"])
  702. for _, name := range columnNames {
  703. if name == indexColumn {
  704. _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
  705. if err != nil {
  706. return err
  707. }
  708. }
  709. }
  710. }
  711. // Here we need to get the columns from the original table
  712. sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
  713. res, err := sess.Query(sql)
  714. if err != nil {
  715. return err
  716. }
  717. tableSQL := string(res[0]["sql"])
  718. // Get the string offset for column definitions: `CREATE TABLE ( column-definitions... )`
  719. columnDefinitionsIndex := strings.Index(tableSQL, "(")
  720. if columnDefinitionsIndex < 0 {
  721. return errors.New("couldn't find column definitions")
  722. }
  723. // Separate out the column definitions
  724. tableSQL = tableSQL[columnDefinitionsIndex:]
  725. // Remove the required columnNames
  726. for _, name := range columnNames {
  727. tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "")
  728. }
  729. // Ensure the query is ended properly
  730. tableSQL = strings.TrimSpace(tableSQL)
  731. if tableSQL[len(tableSQL)-1] != ')' {
  732. if tableSQL[len(tableSQL)-1] == ',' {
  733. tableSQL = tableSQL[:len(tableSQL)-1]
  734. }
  735. tableSQL += ")"
  736. }
  737. // Find all the columns in the table
  738. columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
  739. tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
  740. if _, err := sess.Exec(tableSQL); err != nil {
  741. return err
  742. }
  743. // Now restore the data
  744. columnsSeparated := strings.Join(columns, ",")
  745. insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
  746. if _, err := sess.Exec(insertSQL); err != nil {
  747. return err
  748. }
  749. // Now drop the old table
  750. if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
  751. return err
  752. }
  753. // Rename the table
  754. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
  755. return err
  756. }
  757. case setting.Database.UsePostgreSQL:
  758. cols := ""
  759. for _, col := range columnNames {
  760. if cols != "" {
  761. cols += ", "
  762. }
  763. cols += "DROP COLUMN `" + col + "` CASCADE"
  764. }
  765. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
  766. return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
  767. }
  768. case setting.Database.UseMySQL:
  769. // Drop indexes on columns first
  770. sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
  771. res, err := sess.Query(sql)
  772. if err != nil {
  773. return err
  774. }
  775. for _, index := range res {
  776. indexName := index["column_name"]
  777. if len(indexName) > 0 {
  778. _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, tableName))
  779. if err != nil {
  780. return err
  781. }
  782. }
  783. }
  784. // Now drop the columns
  785. cols := ""
  786. for _, col := range columnNames {
  787. if cols != "" {
  788. cols += ", "
  789. }
  790. cols += "DROP COLUMN `" + col + "`"
  791. }
  792. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
  793. return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
  794. }
  795. case setting.Database.UseMSSQL:
  796. cols := ""
  797. for _, col := range columnNames {
  798. if cols != "" {
  799. cols += ", "
  800. }
  801. cols += "`" + strings.ToLower(col) + "`"
  802. }
  803. sql := fmt.Sprintf("SELECT Name FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('%[1]s') AND parent_column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
  804. tableName, strings.ReplaceAll(cols, "`", "'"))
  805. constraints := make([]string, 0)
  806. if err := sess.SQL(sql).Find(&constraints); err != nil {
  807. return fmt.Errorf("Find constraints: %v", err)
  808. }
  809. for _, constraint := range constraints {
  810. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
  811. return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
  812. }
  813. }
  814. sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
  815. tableName, strings.ReplaceAll(cols, "`", "'"))
  816. constraints = make([]string, 0)
  817. if err := sess.SQL(sql).Find(&constraints); err != nil {
  818. return fmt.Errorf("Find constraints: %v", err)
  819. }
  820. for _, constraint := range constraints {
  821. if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
  822. return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
  823. }
  824. }
  825. if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
  826. return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
  827. }
  828. default:
  829. log.Fatal("Unrecognized DB")
  830. }
  831. return nil
  832. }
  833. // modifyColumn will modify column's type or other propertity. SQLITE is not supported
  834. func modifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
  835. var indexes map[string]*schemas.Index
  836. var err error
  837. // MSSQL have to remove index at first, otherwise alter column will fail
  838. // ref. https://sqlzealots.com/2018/05/09/error-message-the-index-is-dependent-on-column-alter-table-alter-column-failed-because-one-or-more-objects-access-this-column/
  839. if x.Dialect().URI().DBType == schemas.MSSQL {
  840. indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), tableName)
  841. if err != nil {
  842. return err
  843. }
  844. for _, index := range indexes {
  845. _, err = x.Exec(x.Dialect().DropIndexSQL(tableName, index))
  846. if err != nil {
  847. return err
  848. }
  849. }
  850. }
  851. defer func() {
  852. for _, index := range indexes {
  853. _, err = x.Exec(x.Dialect().CreateIndexSQL(tableName, index))
  854. if err != nil {
  855. log.Error("Create index %s on table %s failed: %v", index.Name, tableName, err)
  856. }
  857. }
  858. }()
  859. alterSQL := x.Dialect().ModifyColumnSQL(tableName, col)
  860. if _, err := x.Exec(alterSQL); err != nil {
  861. return err
  862. }
  863. return nil
  864. }