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

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