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.

consistency.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "reflect"
  8. "regexp"
  9. "strings"
  10. "testing"
  11. "code.gitea.io/gitea/modules/setting"
  12. "github.com/stretchr/testify/assert"
  13. "xorm.io/builder"
  14. )
  15. // consistencyCheckable a type that can be tested for database consistency
  16. type consistencyCheckable interface {
  17. checkForConsistency(t *testing.T)
  18. }
  19. // CheckConsistencyForAll test that the entire database is consistent
  20. func CheckConsistencyForAll(t *testing.T) {
  21. CheckConsistencyFor(t,
  22. &User{},
  23. &Repository{},
  24. &Issue{},
  25. &PullRequest{},
  26. &Milestone{},
  27. &Label{},
  28. &Team{},
  29. &Action{})
  30. }
  31. // CheckConsistencyFor test that all matching database entries are consistent
  32. func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) {
  33. for _, bean := range beansToCheck {
  34. sliceType := reflect.SliceOf(reflect.TypeOf(bean))
  35. sliceValue := reflect.MakeSlice(sliceType, 0, 10)
  36. ptrToSliceValue := reflect.New(sliceType)
  37. ptrToSliceValue.Elem().Set(sliceValue)
  38. assert.NoError(t, x.Table(bean).Find(ptrToSliceValue.Interface()))
  39. sliceValue = ptrToSliceValue.Elem()
  40. for i := 0; i < sliceValue.Len(); i++ {
  41. entity := sliceValue.Index(i).Interface()
  42. checkable, ok := entity.(consistencyCheckable)
  43. if !ok {
  44. t.Errorf("Expected %+v (of type %T) to be checkable for consistency",
  45. entity, entity)
  46. } else {
  47. checkable.checkForConsistency(t)
  48. }
  49. }
  50. }
  51. }
  52. // getCount get the count of database entries matching bean
  53. func getCount(t *testing.T, e Engine, bean interface{}) int64 {
  54. count, err := e.Count(bean)
  55. assert.NoError(t, err)
  56. return count
  57. }
  58. // assertCount test the count of database entries matching bean
  59. func assertCount(t *testing.T, bean interface{}, expected int) {
  60. assert.EqualValues(t, expected, getCount(t, x, bean),
  61. "Failed consistency test, the counted bean (of type %T) was %+v", bean, bean)
  62. }
  63. func (user *User) checkForConsistency(t *testing.T) {
  64. assertCount(t, &Repository{OwnerID: user.ID}, user.NumRepos)
  65. assertCount(t, &Star{UID: user.ID}, user.NumStars)
  66. assertCount(t, &OrgUser{OrgID: user.ID}, user.NumMembers)
  67. assertCount(t, &Team{OrgID: user.ID}, user.NumTeams)
  68. assertCount(t, &Follow{UserID: user.ID}, user.NumFollowing)
  69. assertCount(t, &Follow{FollowID: user.ID}, user.NumFollowers)
  70. if user.Type != UserTypeOrganization {
  71. assert.EqualValues(t, 0, user.NumMembers)
  72. assert.EqualValues(t, 0, user.NumTeams)
  73. }
  74. }
  75. func (repo *Repository) checkForConsistency(t *testing.T) {
  76. assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
  77. assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
  78. assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
  79. assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
  80. if repo.IsFork {
  81. AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
  82. }
  83. actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID})
  84. assert.EqualValues(t, repo.NumWatches, actual,
  85. "Unexpected number of watches for repo %+v", repo)
  86. actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
  87. assert.EqualValues(t, repo.NumIssues, actual,
  88. "Unexpected number of issues for repo %+v", repo)
  89. actual = getCount(t, x.Where("is_pull=? AND is_closed=?", false, true), &Issue{RepoID: repo.ID})
  90. assert.EqualValues(t, repo.NumClosedIssues, actual,
  91. "Unexpected number of closed issues for repo %+v", repo)
  92. actual = getCount(t, x.Where("is_pull=?", true), &Issue{RepoID: repo.ID})
  93. assert.EqualValues(t, repo.NumPulls, actual,
  94. "Unexpected number of pulls for repo %+v", repo)
  95. actual = getCount(t, x.Where("is_pull=? AND is_closed=?", true, true), &Issue{RepoID: repo.ID})
  96. assert.EqualValues(t, repo.NumClosedPulls, actual,
  97. "Unexpected number of closed pulls for repo %+v", repo)
  98. actual = getCount(t, x.Where("is_closed=?", true), &Milestone{RepoID: repo.ID})
  99. assert.EqualValues(t, repo.NumClosedMilestones, actual,
  100. "Unexpected number of closed milestones for repo %+v", repo)
  101. }
  102. func (issue *Issue) checkForConsistency(t *testing.T) {
  103. actual := getCount(t, x.Where("type=?", CommentTypeComment), &Comment{IssueID: issue.ID})
  104. assert.EqualValues(t, issue.NumComments, actual,
  105. "Unexpected number of comments for issue %+v", issue)
  106. if issue.IsPull {
  107. pr := AssertExistsAndLoadBean(t, &PullRequest{IssueID: issue.ID}).(*PullRequest)
  108. assert.EqualValues(t, pr.Index, issue.Index)
  109. }
  110. }
  111. func (pr *PullRequest) checkForConsistency(t *testing.T) {
  112. issue := AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue)
  113. assert.True(t, issue.IsPull)
  114. assert.EqualValues(t, issue.Index, pr.Index)
  115. }
  116. func (milestone *Milestone) checkForConsistency(t *testing.T) {
  117. assertCount(t, &Issue{MilestoneID: milestone.ID}, milestone.NumIssues)
  118. actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID})
  119. assert.EqualValues(t, milestone.NumClosedIssues, actual,
  120. "Unexpected number of closed issues for milestone %+v", milestone)
  121. }
  122. func (label *Label) checkForConsistency(t *testing.T) {
  123. issueLabels := make([]*IssueLabel, 0, 10)
  124. assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID}))
  125. assert.EqualValues(t, label.NumIssues, len(issueLabels),
  126. "Unexpected number of issue for label %+v", label)
  127. issueIDs := make([]int64, len(issueLabels))
  128. for i, issueLabel := range issueLabels {
  129. issueIDs[i] = issueLabel.IssueID
  130. }
  131. expected := int64(0)
  132. if len(issueIDs) > 0 {
  133. expected = getCount(t, x.In("id", issueIDs).Where("is_closed=?", true), &Issue{})
  134. }
  135. assert.EqualValues(t, expected, label.NumClosedIssues,
  136. "Unexpected number of closed issues for label %+v", label)
  137. }
  138. func (team *Team) checkForConsistency(t *testing.T) {
  139. assertCount(t, &TeamUser{TeamID: team.ID}, team.NumMembers)
  140. assertCount(t, &TeamRepo{TeamID: team.ID}, team.NumRepos)
  141. }
  142. func (action *Action) checkForConsistency(t *testing.T) {
  143. repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository)
  144. assert.Equal(t, repo.IsPrivate, action.IsPrivate, "action: %+v", action)
  145. }
  146. // CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore
  147. func CountOrphanedLabels() (int64, error) {
  148. noref, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id")
  149. if err != nil {
  150. return 0, err
  151. }
  152. norepo, err := x.Table("label").
  153. Join("LEFT", "repository", "label.repo_id=repository.id").
  154. Where(builder.IsNull{"repository.id"}).And(builder.Gt{"label.repo_id": 0}).
  155. Count("id")
  156. if err != nil {
  157. return 0, err
  158. }
  159. noorg, err := x.Table("label").
  160. Join("LEFT", "`user`", "label.org_id=`user`.id").
  161. Where(builder.IsNull{"`user`.id"}).And(builder.Gt{"label.org_id": 0}).
  162. Count("id")
  163. if err != nil {
  164. return 0, err
  165. }
  166. return noref + norepo + noorg, nil
  167. }
  168. // DeleteOrphanedLabels delete labels witch are broken and not accessible via ui anymore
  169. func DeleteOrphanedLabels() error {
  170. // delete labels with no reference
  171. if _, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil {
  172. return err
  173. }
  174. // delete labels with none existing repos
  175. if _, err := x.In("id", builder.Select("label.id").From("label").
  176. Join("LEFT", "repository", "label.repo_id=repository.id").
  177. Where(builder.IsNull{"repository.id"}).And(builder.Gt{"label.repo_id": 0})).
  178. Delete(Label{}); err != nil {
  179. return err
  180. }
  181. // delete labels with none existing orgs
  182. if _, err := x.In("id", builder.Select("label.id").From("label").
  183. Join("LEFT", "`user`", "label.org_id=`user`.id").
  184. Where(builder.IsNull{"`user`.id"}).And(builder.Gt{"label.org_id": 0})).
  185. Delete(Label{}); err != nil {
  186. return err
  187. }
  188. return nil
  189. }
  190. // CountOrphanedIssueLabels return count of IssueLabels witch have no label behind anymore
  191. func CountOrphanedIssueLabels() (int64, error) {
  192. return x.Table("issue_label").
  193. Join("LEFT", "label", "issue_label.label_id = label.id").
  194. Where(builder.IsNull{"label.id"}).Count()
  195. }
  196. // DeleteOrphanedIssueLabels delete IssueLabels witch have no label behind anymore
  197. func DeleteOrphanedIssueLabels() error {
  198. _, err := x.In("id", builder.Select("issue_label.id").From("issue_label").
  199. Join("LEFT", "label", "issue_label.label_id = label.id").
  200. Where(builder.IsNull{"label.id"})).
  201. Delete(IssueLabel{})
  202. return err
  203. }
  204. // CountOrphanedIssues count issues without a repo
  205. func CountOrphanedIssues() (int64, error) {
  206. return x.Table("issue").
  207. Join("LEFT", "repository", "issue.repo_id=repository.id").
  208. Where(builder.IsNull{"repository.id"}).
  209. Count("id")
  210. }
  211. // DeleteOrphanedIssues delete issues without a repo
  212. func DeleteOrphanedIssues() error {
  213. sess := x.NewSession()
  214. defer sess.Close()
  215. if err := sess.Begin(); err != nil {
  216. return err
  217. }
  218. var ids []int64
  219. if err := sess.Table("issue").Distinct("issue.repo_id").
  220. Join("LEFT", "repository", "issue.repo_id=repository.id").
  221. Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
  222. Find(&ids); err != nil {
  223. return err
  224. }
  225. var attachmentPaths []string
  226. for i := range ids {
  227. paths, err := deleteIssuesByRepoID(sess, ids[i])
  228. if err != nil {
  229. return err
  230. }
  231. attachmentPaths = append(attachmentPaths, paths...)
  232. }
  233. if err := sess.Commit(); err != nil {
  234. return err
  235. }
  236. // Remove issue attachment files.
  237. for i := range attachmentPaths {
  238. removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i])
  239. }
  240. return nil
  241. }
  242. // CountOrphanedObjects count subjects with have no existing refobject anymore
  243. func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
  244. return x.Table("`"+subject+"`").
  245. Join("LEFT", refobject, joinCond).
  246. Where(builder.IsNull{"`" + refobject + "`.id"}).
  247. Count("id")
  248. }
  249. // DeleteOrphanedObjects delete subjects with have no existing refobject anymore
  250. func DeleteOrphanedObjects(subject, refobject, joinCond string) error {
  251. _, err := x.In("id", builder.Select("`"+subject+"`.id").
  252. From("`"+subject+"`").
  253. Join("LEFT", "`"+refobject+"`", joinCond).
  254. Where(builder.IsNull{"`" + refobject + "`.id"})).
  255. Delete("`" + subject + "`")
  256. return err
  257. }
  258. // CountNullArchivedRepository counts the number of repositories with is_archived is null
  259. func CountNullArchivedRepository() (int64, error) {
  260. return x.Where(builder.IsNull{"is_archived"}).Count(new(Repository))
  261. }
  262. // FixNullArchivedRepository sets is_archived to false where it is null
  263. func FixNullArchivedRepository() (int64, error) {
  264. return x.Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
  265. IsArchived: false,
  266. })
  267. }
  268. // CountWrongUserType count OrgUser who have wrong type
  269. func CountWrongUserType() (int64, error) {
  270. return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User))
  271. }
  272. // FixWrongUserType fix OrgUser who have wrong type
  273. func FixWrongUserType() (int64, error) {
  274. return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1})
  275. }
  276. // CountCommentTypeLabelWithEmptyLabel count label comments with empty label
  277. func CountCommentTypeLabelWithEmptyLabel() (int64, error) {
  278. return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Count(new(Comment))
  279. }
  280. // FixCommentTypeLabelWithEmptyLabel count label comments with empty label
  281. func FixCommentTypeLabelWithEmptyLabel() (int64, error) {
  282. return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Delete(new(Comment))
  283. }
  284. // CountCommentTypeLabelWithOutsideLabels count label comments with outside label
  285. func CountCommentTypeLabelWithOutsideLabels() (int64, error) {
  286. return x.Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel).
  287. Table("comment").
  288. Join("inner", "label", "label.id = comment.label_id").
  289. Join("inner", "issue", "issue.id = comment.issue_id ").
  290. Join("inner", "repository", "issue.repo_id = repository.id").
  291. Count(new(Comment))
  292. }
  293. // FixCommentTypeLabelWithOutsideLabels count label comments with outside label
  294. func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
  295. res, err := x.Exec(`DELETE FROM comment WHERE comment.id IN (
  296. SELECT il_too.id FROM (
  297. SELECT com.id
  298. FROM comment AS com
  299. INNER JOIN label ON com.label_id = label.id
  300. INNER JOIN issue on issue.id = com.issue_id
  301. INNER JOIN repository ON issue.repo_id = repository.id
  302. WHERE
  303. com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
  304. ) AS il_too)`, CommentTypeLabel)
  305. if err != nil {
  306. return 0, err
  307. }
  308. return res.RowsAffected()
  309. }
  310. // CountIssueLabelWithOutsideLabels count label comments with outside label
  311. func CountIssueLabelWithOutsideLabels() (int64, error) {
  312. return x.Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")).
  313. Table("issue_label").
  314. Join("inner", "label", "issue_label.label_id = label.id ").
  315. Join("inner", "issue", "issue.id = issue_label.issue_id ").
  316. Join("inner", "repository", "issue.repo_id = repository.id").
  317. Count(new(IssueLabel))
  318. }
  319. // FixIssueLabelWithOutsideLabels fix label comments with outside label
  320. func FixIssueLabelWithOutsideLabels() (int64, error) {
  321. res, err := x.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
  322. SELECT il_too.id FROM (
  323. SELECT il_too_too.id
  324. FROM issue_label AS il_too_too
  325. INNER JOIN label ON il_too_too.label_id = label.id
  326. INNER JOIN issue on issue.id = il_too_too.issue_id
  327. INNER JOIN repository on repository.id = issue.repo_id
  328. WHERE
  329. (label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
  330. ) AS il_too )`)
  331. if err != nil {
  332. return 0, err
  333. }
  334. return res.RowsAffected()
  335. }
  336. // CountBadSequences looks for broken sequences from recreate-table mistakes
  337. func CountBadSequences() (int64, error) {
  338. if !setting.Database.UsePostgreSQL {
  339. return 0, nil
  340. }
  341. sess := x.NewSession()
  342. defer sess.Close()
  343. var sequences []string
  344. schema := sess.Engine().Dialect().URI().Schema
  345. sess.Engine().SetSchema("")
  346. if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil {
  347. return 0, err
  348. }
  349. sess.Engine().SetSchema(schema)
  350. return int64(len(sequences)), nil
  351. }
  352. // FixBadSequences fixes for broken sequences from recreate-table mistakes
  353. func FixBadSequences() error {
  354. if !setting.Database.UsePostgreSQL {
  355. return nil
  356. }
  357. sess := x.NewSession()
  358. defer sess.Close()
  359. if err := sess.Begin(); err != nil {
  360. return err
  361. }
  362. var sequences []string
  363. schema := sess.Engine().Dialect().URI().Schema
  364. sess.Engine().SetSchema("")
  365. if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil {
  366. return err
  367. }
  368. sess.Engine().SetSchema(schema)
  369. sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`)
  370. for _, sequence := range sequences {
  371. tableName := sequenceRegexp.FindStringSubmatch(sequence)[1]
  372. newSequenceName := tableName + "_id_seq"
  373. if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
  374. return err
  375. }
  376. if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
  377. return err
  378. }
  379. }
  380. return sess.Commit()
  381. }