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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. completeness := 0
  122. if milestone.NumIssues > 0 {
  123. completeness = milestone.NumClosedIssues * 100 / milestone.NumIssues
  124. }
  125. assert.Equal(t, completeness, milestone.Completeness)
  126. }
  127. func (label *Label) checkForConsistency(t *testing.T) {
  128. issueLabels := make([]*IssueLabel, 0, 10)
  129. assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID}))
  130. assert.EqualValues(t, label.NumIssues, len(issueLabels),
  131. "Unexpected number of issue for label %+v", label)
  132. issueIDs := make([]int64, len(issueLabels))
  133. for i, issueLabel := range issueLabels {
  134. issueIDs[i] = issueLabel.IssueID
  135. }
  136. expected := int64(0)
  137. if len(issueIDs) > 0 {
  138. expected = getCount(t, x.In("id", issueIDs).Where("is_closed=?", true), &Issue{})
  139. }
  140. assert.EqualValues(t, expected, label.NumClosedIssues,
  141. "Unexpected number of closed issues for label %+v", label)
  142. }
  143. func (team *Team) checkForConsistency(t *testing.T) {
  144. assertCount(t, &TeamUser{TeamID: team.ID}, team.NumMembers)
  145. assertCount(t, &TeamRepo{TeamID: team.ID}, team.NumRepos)
  146. }
  147. func (action *Action) checkForConsistency(t *testing.T) {
  148. repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository)
  149. assert.Equal(t, repo.IsPrivate, action.IsPrivate, "action: %+v", action)
  150. }
  151. // CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore
  152. func CountOrphanedLabels() (int64, error) {
  153. noref, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id")
  154. if err != nil {
  155. return 0, err
  156. }
  157. norepo, err := x.Table("label").
  158. Where(builder.And(
  159. builder.Gt{"repo_id": 0},
  160. builder.NotIn("repo_id", builder.Select("id").From("repository")),
  161. )).
  162. Count()
  163. if err != nil {
  164. return 0, err
  165. }
  166. noorg, err := x.Table("label").
  167. Where(builder.And(
  168. builder.Gt{"org_id": 0},
  169. builder.NotIn("org_id", builder.Select("id").From("user")),
  170. )).
  171. Count()
  172. if err != nil {
  173. return 0, err
  174. }
  175. return noref + norepo + noorg, nil
  176. }
  177. // DeleteOrphanedLabels delete labels witch are broken and not accessible via ui anymore
  178. func DeleteOrphanedLabels() error {
  179. // delete labels with no reference
  180. if _, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil {
  181. return err
  182. }
  183. // delete labels with none existing repos
  184. if _, err := x.
  185. Where(builder.And(
  186. builder.Gt{"repo_id": 0},
  187. builder.NotIn("repo_id", builder.Select("id").From("repository")),
  188. )).
  189. Delete(Label{}); err != nil {
  190. return err
  191. }
  192. // delete labels with none existing orgs
  193. if _, err := x.
  194. Where(builder.And(
  195. builder.Gt{"org_id": 0},
  196. builder.NotIn("org_id", builder.Select("id").From("user")),
  197. )).
  198. Delete(Label{}); err != nil {
  199. return err
  200. }
  201. return nil
  202. }
  203. // CountOrphanedIssueLabels return count of IssueLabels witch have no label behind anymore
  204. func CountOrphanedIssueLabels() (int64, error) {
  205. return x.Table("issue_label").
  206. NotIn("label_id", builder.Select("id").From("label")).
  207. Count()
  208. }
  209. // DeleteOrphanedIssueLabels delete IssueLabels witch have no label behind anymore
  210. func DeleteOrphanedIssueLabels() error {
  211. _, err := x.
  212. NotIn("label_id", builder.Select("id").From("label")).
  213. Delete(IssueLabel{})
  214. return err
  215. }
  216. // CountOrphanedIssues count issues without a repo
  217. func CountOrphanedIssues() (int64, error) {
  218. return x.Table("issue").
  219. Join("LEFT", "repository", "issue.repo_id=repository.id").
  220. Where(builder.IsNull{"repository.id"}).
  221. Count("id")
  222. }
  223. // DeleteOrphanedIssues delete issues without a repo
  224. func DeleteOrphanedIssues() error {
  225. sess := x.NewSession()
  226. defer sess.Close()
  227. if err := sess.Begin(); err != nil {
  228. return err
  229. }
  230. var ids []int64
  231. if err := sess.Table("issue").Distinct("issue.repo_id").
  232. Join("LEFT", "repository", "issue.repo_id=repository.id").
  233. Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
  234. Find(&ids); err != nil {
  235. return err
  236. }
  237. var attachmentPaths []string
  238. for i := range ids {
  239. paths, err := deleteIssuesByRepoID(sess, ids[i])
  240. if err != nil {
  241. return err
  242. }
  243. attachmentPaths = append(attachmentPaths, paths...)
  244. }
  245. if err := sess.Commit(); err != nil {
  246. return err
  247. }
  248. // Remove issue attachment files.
  249. for i := range attachmentPaths {
  250. removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i])
  251. }
  252. return nil
  253. }
  254. // CountOrphanedObjects count subjects with have no existing refobject anymore
  255. func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
  256. return x.Table("`"+subject+"`").
  257. Join("LEFT", refobject, joinCond).
  258. Where(builder.IsNull{"`" + refobject + "`.id"}).
  259. Count("id")
  260. }
  261. // DeleteOrphanedObjects delete subjects with have no existing refobject anymore
  262. func DeleteOrphanedObjects(subject, refobject, joinCond string) error {
  263. subQuery := builder.Select("`"+subject+"`.id").
  264. From("`"+subject+"`").
  265. Join("LEFT", "`"+refobject+"`", joinCond).
  266. Where(builder.IsNull{"`" + refobject + "`.id"})
  267. sql, args, err := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`").ToSQL()
  268. if err != nil {
  269. return err
  270. }
  271. _, err = x.Exec(append([]interface{}{sql}, args...)...)
  272. return err
  273. }
  274. // CountNullArchivedRepository counts the number of repositories with is_archived is null
  275. func CountNullArchivedRepository() (int64, error) {
  276. return x.Where(builder.IsNull{"is_archived"}).Count(new(Repository))
  277. }
  278. // FixNullArchivedRepository sets is_archived to false where it is null
  279. func FixNullArchivedRepository() (int64, error) {
  280. return x.Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
  281. IsArchived: false,
  282. })
  283. }
  284. // CountWrongUserType count OrgUser who have wrong type
  285. func CountWrongUserType() (int64, error) {
  286. return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User))
  287. }
  288. // FixWrongUserType fix OrgUser who have wrong type
  289. func FixWrongUserType() (int64, error) {
  290. return x.Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1})
  291. }
  292. // CountCommentTypeLabelWithEmptyLabel count label comments with empty label
  293. func CountCommentTypeLabelWithEmptyLabel() (int64, error) {
  294. return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Count(new(Comment))
  295. }
  296. // FixCommentTypeLabelWithEmptyLabel count label comments with empty label
  297. func FixCommentTypeLabelWithEmptyLabel() (int64, error) {
  298. return x.Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Delete(new(Comment))
  299. }
  300. // CountCommentTypeLabelWithOutsideLabels count label comments with outside label
  301. func CountCommentTypeLabelWithOutsideLabels() (int64, error) {
  302. 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).
  303. Table("comment").
  304. Join("inner", "label", "label.id = comment.label_id").
  305. Join("inner", "issue", "issue.id = comment.issue_id ").
  306. Join("inner", "repository", "issue.repo_id = repository.id").
  307. Count(new(Comment))
  308. }
  309. // FixCommentTypeLabelWithOutsideLabels count label comments with outside label
  310. func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
  311. res, err := x.Exec(`DELETE FROM comment WHERE comment.id IN (
  312. SELECT il_too.id FROM (
  313. SELECT com.id
  314. FROM comment AS com
  315. INNER JOIN label ON com.label_id = label.id
  316. INNER JOIN issue on issue.id = com.issue_id
  317. INNER JOIN repository ON issue.repo_id = repository.id
  318. WHERE
  319. 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))
  320. ) AS il_too)`, CommentTypeLabel)
  321. if err != nil {
  322. return 0, err
  323. }
  324. return res.RowsAffected()
  325. }
  326. // CountIssueLabelWithOutsideLabels count label comments with outside label
  327. func CountIssueLabelWithOutsideLabels() (int64, error) {
  328. 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)")).
  329. Table("issue_label").
  330. Join("inner", "label", "issue_label.label_id = label.id ").
  331. Join("inner", "issue", "issue.id = issue_label.issue_id ").
  332. Join("inner", "repository", "issue.repo_id = repository.id").
  333. Count(new(IssueLabel))
  334. }
  335. // FixIssueLabelWithOutsideLabels fix label comments with outside label
  336. func FixIssueLabelWithOutsideLabels() (int64, error) {
  337. res, err := x.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
  338. SELECT il_too.id FROM (
  339. SELECT il_too_too.id
  340. FROM issue_label AS il_too_too
  341. INNER JOIN label ON il_too_too.label_id = label.id
  342. INNER JOIN issue on issue.id = il_too_too.issue_id
  343. INNER JOIN repository on repository.id = issue.repo_id
  344. WHERE
  345. (label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
  346. ) AS il_too )`)
  347. if err != nil {
  348. return 0, err
  349. }
  350. return res.RowsAffected()
  351. }
  352. // CountBadSequences looks for broken sequences from recreate-table mistakes
  353. func CountBadSequences() (int64, error) {
  354. if !setting.Database.UsePostgreSQL {
  355. return 0, nil
  356. }
  357. sess := x.NewSession()
  358. defer sess.Close()
  359. var sequences []string
  360. schema := sess.Engine().Dialect().URI().Schema
  361. sess.Engine().SetSchema("")
  362. 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 {
  363. return 0, err
  364. }
  365. sess.Engine().SetSchema(schema)
  366. return int64(len(sequences)), nil
  367. }
  368. // FixBadSequences fixes for broken sequences from recreate-table mistakes
  369. func FixBadSequences() error {
  370. if !setting.Database.UsePostgreSQL {
  371. return nil
  372. }
  373. sess := x.NewSession()
  374. defer sess.Close()
  375. if err := sess.Begin(); err != nil {
  376. return err
  377. }
  378. var sequences []string
  379. schema := sess.Engine().Dialect().URI().Schema
  380. sess.Engine().SetSchema("")
  381. 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 {
  382. return err
  383. }
  384. sess.Engine().SetSchema(schema)
  385. sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`)
  386. for _, sequence := range sequences {
  387. tableName := sequenceRegexp.FindStringSubmatch(sequence)[1]
  388. newSequenceName := tableName + "_id_seq"
  389. if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
  390. return err
  391. }
  392. if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
  393. return err
  394. }
  395. }
  396. return sess.Commit()
  397. }