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 35KB

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