2024-02-18 19:39:41 -05:00
|
|
|
package database
|
2014-02-13 23:23:23 +08:00
|
|
|
|
2014-02-18 17:48:02 -05:00
|
|
|
import (
|
2022-06-10 19:54:36 +08:00
|
|
|
"context"
|
2014-02-18 17:48:02 -05:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
2014-03-21 01:09:22 -04:00
|
|
|
"path"
|
2020-09-29 22:26:07 +08:00
|
|
|
"path/filepath"
|
2014-04-14 14:49:50 +08:00
|
|
|
"strings"
|
2019-02-06 18:46:15 -05:00
|
|
|
"time"
|
2014-02-18 17:48:02 -05:00
|
|
|
|
2026-01-22 08:20:53 -05:00
|
|
|
"github.com/cockroachdb/errors"
|
2020-09-06 10:11:08 +08:00
|
|
|
"gorm.io/gorm"
|
2020-09-29 22:26:07 +08:00
|
|
|
"gorm.io/gorm/logger"
|
2026-01-24 00:42:51 +00:00
|
|
|
"gorm.io/gorm/schema"
|
2020-02-20 02:25:02 +08:00
|
|
|
log "unknwon.dev/clog/v2"
|
2014-02-18 17:48:02 -05:00
|
|
|
|
2020-02-22 09:05:26 +08:00
|
|
|
"gogs.io/gogs/internal/conf"
|
2024-02-18 19:39:41 -05:00
|
|
|
"gogs.io/gogs/internal/database/migrations"
|
2020-09-29 22:26:07 +08:00
|
|
|
"gogs.io/gogs/internal/dbutil"
|
2014-02-18 17:48:02 -05:00
|
|
|
)
|
2014-02-13 23:23:23 +08:00
|
|
|
|
2014-03-21 01:48:10 -04:00
|
|
|
var (
|
2026-01-24 00:42:51 +00:00
|
|
|
db *gorm.DB
|
2023-02-02 21:25:25 +08:00
|
|
|
legacyTables []any
|
2020-04-11 01:25:19 +08:00
|
|
|
HasEngine bool
|
2014-03-21 01:48:10 -04:00
|
|
|
)
|
|
|
|
|
|
2014-04-05 22:46:32 +08:00
|
|
|
func init() {
|
2020-04-11 01:25:19 +08:00
|
|
|
legacyTables = append(legacyTables,
|
|
|
|
|
new(User), new(PublicKey), new(TwoFactor), new(TwoFactorRecoveryCode),
|
2020-10-06 15:43:28 +08:00
|
|
|
new(Repository), new(DeployKey), new(Collaboration), new(Upload),
|
2022-10-23 16:17:53 +08:00
|
|
|
new(Watch), new(Star),
|
2015-09-02 05:09:12 -04:00
|
|
|
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
2015-08-10 14:42:50 +08:00
|
|
|
new(Label), new(IssueLabel), new(Milestone),
|
2020-04-11 20:18:05 +08:00
|
|
|
new(Mirror), new(Release), new(Webhook), new(HookTask),
|
2017-02-23 18:25:12 -05:00
|
|
|
new(ProtectBranch), new(ProtectBranchWhitelist),
|
2015-02-23 02:15:53 -05:00
|
|
|
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
2023-08-23 00:15:30 -04:00
|
|
|
)
|
2026-01-24 00:42:51 +00:00
|
|
|
}
|
2015-08-27 23:06:14 +08:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
func getGormDB(gormLogger logger.Writer) (*gorm.DB, error) {
|
|
|
|
|
if conf.Database.Type == "sqlite3" {
|
|
|
|
|
if err := os.MkdirAll(path.Dir(conf.Database.Path), os.ModePerm); err != nil {
|
|
|
|
|
return nil, errors.Newf("create directories: %v", err)
|
|
|
|
|
}
|
2015-08-27 23:06:14 +08:00
|
|
|
}
|
2014-04-05 22:46:32 +08:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
level := logger.Info
|
|
|
|
|
if conf.IsProdMode() {
|
|
|
|
|
level = logger.Warn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.Default = logger.New(gormLogger, logger.Config{
|
|
|
|
|
SlowThreshold: 100 * time.Millisecond,
|
|
|
|
|
LogLevel: level,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
gormDB, err := dbutil.OpenDB(
|
|
|
|
|
conf.Database,
|
|
|
|
|
&gorm.Config{
|
|
|
|
|
SkipDefaultTransaction: true,
|
|
|
|
|
NamingStrategy: schema.NamingStrategy{
|
|
|
|
|
SingularTable: true,
|
|
|
|
|
},
|
|
|
|
|
NowFunc: func() time.Time {
|
|
|
|
|
return time.Now().UTC().Truncate(time.Microsecond)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "open database")
|
2016-07-02 10:39:39 -04:00
|
|
|
}
|
2020-02-22 18:58:16 +08:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
sqlDB, err := gormDB.DB()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "get underlying *sql.DB")
|
|
|
|
|
}
|
|
|
|
|
sqlDB.SetMaxOpenConns(conf.Database.MaxOpenConns)
|
|
|
|
|
sqlDB.SetMaxIdleConns(conf.Database.MaxIdleConns)
|
|
|
|
|
sqlDB.SetConnMaxLifetime(time.Minute)
|
2020-02-22 18:58:16 +08:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
switch conf.Database.Type {
|
2014-03-30 10:47:08 -04:00
|
|
|
case "postgres":
|
2020-02-22 18:58:16 +08:00
|
|
|
conf.UsePostgreSQL = true
|
2026-01-24 00:42:51 +00:00
|
|
|
case "mysql":
|
|
|
|
|
conf.UseMySQL = true
|
|
|
|
|
gormDB = gormDB.Set("gorm:table_options", "ENGINE=InnoDB").Session(&gorm.Session{})
|
2014-04-12 11:48:12 -07:00
|
|
|
case "sqlite3":
|
2020-02-22 18:58:16 +08:00
|
|
|
conf.UseSQLite3 = true
|
2026-01-24 00:42:51 +00:00
|
|
|
case "mssql":
|
|
|
|
|
conf.UseMSSQL = true
|
2014-03-30 10:47:08 -04:00
|
|
|
}
|
2026-01-24 00:42:51 +00:00
|
|
|
|
|
|
|
|
return gormDB, nil
|
2014-09-04 17:19:26 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-21 13:39:32 +08:00
|
|
|
func NewTestEngine() error {
|
2026-01-24 00:42:51 +00:00
|
|
|
var err error
|
|
|
|
|
db, err = getGormDB(&dbutil.Logger{Writer: log.NewConsoleWriter()})
|
2014-03-30 10:47:08 -04:00
|
|
|
if err != nil {
|
2026-01-22 08:20:53 -05:00
|
|
|
return errors.Newf("connect to database: %v", err)
|
2014-03-30 10:47:08 -04:00
|
|
|
}
|
2014-09-04 17:19:26 +02:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
for _, table := range legacyTables {
|
|
|
|
|
if db.Migrator().HasTable(table) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err = db.Migrator().AutoMigrate(table); err != nil {
|
|
|
|
|
return errors.Wrap(err, "auto migrate")
|
|
|
|
|
}
|
2022-01-05 22:02:33 +08:00
|
|
|
}
|
2026-01-24 00:42:51 +00:00
|
|
|
return nil
|
2014-03-30 10:47:08 -04:00
|
|
|
}
|
|
|
|
|
|
2020-05-04 16:25:57 +08:00
|
|
|
func SetEngine() (*gorm.DB, error) {
|
2020-09-29 22:26:07 +08:00
|
|
|
var logPath string
|
|
|
|
|
if conf.HookMode {
|
2026-01-24 00:42:51 +00:00
|
|
|
logPath = filepath.Join(conf.Log.RootPath, "hooks", "gorm.log")
|
2020-09-29 22:26:07 +08:00
|
|
|
} else {
|
2026-01-24 00:42:51 +00:00
|
|
|
logPath = filepath.Join(conf.Log.RootPath, "gorm.log")
|
2020-09-29 22:26:07 +08:00
|
|
|
}
|
2026-01-24 00:42:51 +00:00
|
|
|
sec := conf.File.Section("log.gorm")
|
2020-09-29 22:26:07 +08:00
|
|
|
fileWriter, err := log.NewFileWriter(logPath,
|
2017-03-23 18:34:25 -04:00
|
|
|
log.FileRotationConfig{
|
|
|
|
|
Rotate: sec.Key("ROTATE").MustBool(true),
|
|
|
|
|
Daily: sec.Key("ROTATE_DAILY").MustBool(true),
|
|
|
|
|
MaxSize: sec.Key("MAX_SIZE").MustInt64(100) * 1024 * 1024,
|
|
|
|
|
MaxDays: sec.Key("MAX_DAYS").MustInt64(3),
|
2020-09-29 22:26:07 +08:00
|
|
|
},
|
|
|
|
|
)
|
2014-03-17 14:03:58 -04:00
|
|
|
if err != nil {
|
2026-01-24 00:42:51 +00:00
|
|
|
return nil, errors.Newf("create 'gorm.log': %v", err)
|
2014-03-17 14:03:58 -04:00
|
|
|
}
|
2017-02-09 15:09:37 -05:00
|
|
|
|
2020-09-29 22:26:07 +08:00
|
|
|
var gormLogger logger.Writer
|
|
|
|
|
if conf.HookMode {
|
|
|
|
|
gormLogger = &dbutil.Logger{Writer: fileWriter}
|
|
|
|
|
} else {
|
|
|
|
|
gormLogger, err = newLogWriter()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "new log writer")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-24 00:42:51 +00:00
|
|
|
|
|
|
|
|
db, err = getGormDB(gormLogger)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 21:47:32 -05:00
|
|
|
return NewConnection(gormLogger)
|
2014-02-18 17:48:02 -05:00
|
|
|
}
|
|
|
|
|
|
2024-03-20 19:02:57 -04:00
|
|
|
func NewEngine() error {
|
2026-01-24 00:42:51 +00:00
|
|
|
gormDB, err := SetEngine()
|
2022-06-05 13:34:21 +08:00
|
|
|
if err != nil {
|
2024-03-20 19:02:57 -04:00
|
|
|
return err
|
2014-04-05 22:46:32 +08:00
|
|
|
}
|
2015-01-22 14:49:52 +02:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
if err = migrations.Migrate(gormDB); err != nil {
|
2026-01-22 08:20:53 -05:00
|
|
|
return errors.Newf("migrate: %v", err)
|
2015-01-22 14:49:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
for _, table := range legacyTables {
|
|
|
|
|
if gormDB.Migrator().HasTable(table) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
name := strings.TrimPrefix(fmt.Sprintf("%T", table), "*database.")
|
|
|
|
|
if err = gormDB.Migrator().AutoMigrate(table); err != nil {
|
|
|
|
|
return errors.Wrapf(err, "auto migrate %q", name)
|
|
|
|
|
}
|
|
|
|
|
log.Trace("Auto migrated %q", name)
|
2014-02-19 17:50:53 +08:00
|
|
|
}
|
2015-01-23 09:54:16 +02:00
|
|
|
|
2026-01-24 00:42:51 +00:00
|
|
|
HasEngine = true
|
2024-03-20 19:02:57 -04:00
|
|
|
return nil
|
2014-02-18 17:48:02 -05:00
|
|
|
}
|
2014-03-20 16:04:56 -04:00
|
|
|
|
|
|
|
|
type Statistic struct {
|
|
|
|
|
Counter struct {
|
2014-08-28 22:29:00 +08:00
|
|
|
User, Org, PublicKey,
|
|
|
|
|
Repo, Watch, Star, Action, Access,
|
|
|
|
|
Issue, Comment, Oauth, Follow,
|
|
|
|
|
Mirror, Release, LoginSource, Webhook,
|
|
|
|
|
Milestone, Label, HookTask,
|
|
|
|
|
Team, UpdateTask, Attachment int64
|
2014-03-20 16:04:56 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 19:54:36 +08:00
|
|
|
func GetStatistic(ctx context.Context) (stats Statistic) {
|
2024-03-27 23:18:59 -04:00
|
|
|
stats.Counter.User = Handle.Users().Count(ctx)
|
2014-08-28 22:29:00 +08:00
|
|
|
stats.Counter.Org = CountOrganizations()
|
2026-01-24 00:42:51 +00:00
|
|
|
var count int64
|
|
|
|
|
db.Model(new(PublicKey)).Count(&count)
|
|
|
|
|
stats.Counter.PublicKey = count
|
2016-07-24 14:32:46 +08:00
|
|
|
stats.Counter.Repo = CountRepositories(true)
|
2026-01-24 00:42:51 +00:00
|
|
|
db.Model(new(Watch)).Count(&count)
|
|
|
|
|
stats.Counter.Watch = count
|
|
|
|
|
db.Model(new(Star)).Count(&count)
|
|
|
|
|
stats.Counter.Star = count
|
|
|
|
|
db.Model(new(Action)).Count(&count)
|
|
|
|
|
stats.Counter.Action = count
|
|
|
|
|
db.Model(new(Access)).Count(&count)
|
|
|
|
|
stats.Counter.Access = count
|
|
|
|
|
db.Model(new(Issue)).Count(&count)
|
|
|
|
|
stats.Counter.Issue = count
|
|
|
|
|
db.Model(new(Comment)).Count(&count)
|
|
|
|
|
stats.Counter.Comment = count
|
2015-09-17 16:11:44 -04:00
|
|
|
stats.Counter.Oauth = 0
|
2026-01-24 00:42:51 +00:00
|
|
|
db.Model(new(Follow)).Count(&count)
|
|
|
|
|
stats.Counter.Follow = count
|
|
|
|
|
db.Model(new(Mirror)).Count(&count)
|
|
|
|
|
stats.Counter.Mirror = count
|
|
|
|
|
db.Model(new(Release)).Count(&count)
|
|
|
|
|
stats.Counter.Release = count
|
2024-03-17 20:14:54 -04:00
|
|
|
stats.Counter.LoginSource = Handle.LoginSources().Count(ctx)
|
2026-01-24 00:42:51 +00:00
|
|
|
db.Model(new(Webhook)).Count(&count)
|
|
|
|
|
stats.Counter.Webhook = count
|
|
|
|
|
db.Model(new(Milestone)).Count(&count)
|
|
|
|
|
stats.Counter.Milestone = count
|
|
|
|
|
db.Model(new(Label)).Count(&count)
|
|
|
|
|
stats.Counter.Label = count
|
|
|
|
|
db.Model(new(HookTask)).Count(&count)
|
|
|
|
|
stats.Counter.HookTask = count
|
|
|
|
|
db.Model(new(Team)).Count(&count)
|
|
|
|
|
stats.Counter.Team = count
|
|
|
|
|
db.Model(new(Attachment)).Count(&count)
|
|
|
|
|
stats.Counter.Attachment = count
|
2021-05-19 13:38:13 +08:00
|
|
|
return stats
|
2014-03-20 16:04:56 -04:00
|
|
|
}
|
2014-05-05 00:55:17 -04:00
|
|
|
|
2014-08-06 17:21:24 -04:00
|
|
|
func Ping() error {
|
2026-01-24 00:42:51 +00:00
|
|
|
if db == nil {
|
2021-12-20 18:46:54 +08:00
|
|
|
return errors.New("database not available")
|
|
|
|
|
}
|
2026-01-24 00:42:51 +00:00
|
|
|
sqlDB, err := db.DB()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return sqlDB.Ping()
|
2014-08-06 17:21:24 -04:00
|
|
|
}
|
|
|
|
|
|
2017-02-26 04:37:05 -05:00
|
|
|
// The version table. Should have only one row with id==1
|
|
|
|
|
type Version struct {
|
|
|
|
|
ID int64
|
|
|
|
|
Version int64
|
|
|
|
|
}
|