code.gitea.io/gitea@v1.21.7/models/db/engine.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package db
     6  
     7  import (
     8  	"context"
     9  	"database/sql"
    10  	"fmt"
    11  	"io"
    12  	"reflect"
    13  	"strings"
    14  
    15  	"code.gitea.io/gitea/modules/setting"
    16  
    17  	"xorm.io/xorm"
    18  	"xorm.io/xorm/names"
    19  	"xorm.io/xorm/schemas"
    20  
    21  	_ "github.com/denisenkom/go-mssqldb" // Needed for the MSSQL driver
    22  	_ "github.com/go-sql-driver/mysql"   // Needed for the MySQL driver
    23  	_ "github.com/lib/pq"                // Needed for the Postgresql driver
    24  )
    25  
    26  var (
    27  	x         *xorm.Engine
    28  	tables    []any
    29  	initFuncs []func() error
    30  )
    31  
    32  // Engine represents a xorm engine or session.
    33  type Engine interface {
    34  	Table(tableNameOrBean any) *xorm.Session
    35  	Count(...any) (int64, error)
    36  	Decr(column string, arg ...any) *xorm.Session
    37  	Delete(...any) (int64, error)
    38  	Truncate(...any) (int64, error)
    39  	Exec(...any) (sql.Result, error)
    40  	Find(any, ...any) error
    41  	Get(beans ...any) (bool, error)
    42  	ID(any) *xorm.Session
    43  	In(string, ...any) *xorm.Session
    44  	Incr(column string, arg ...any) *xorm.Session
    45  	Insert(...any) (int64, error)
    46  	Iterate(any, xorm.IterFunc) error
    47  	Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
    48  	SQL(any, ...any) *xorm.Session
    49  	Where(any, ...any) *xorm.Session
    50  	Asc(colNames ...string) *xorm.Session
    51  	Desc(colNames ...string) *xorm.Session
    52  	Limit(limit int, start ...int) *xorm.Session
    53  	NoAutoTime() *xorm.Session
    54  	SumInt(bean any, columnName string) (res int64, err error)
    55  	Sync(...any) error
    56  	Select(string) *xorm.Session
    57  	NotIn(string, ...any) *xorm.Session
    58  	OrderBy(any, ...any) *xorm.Session
    59  	Exist(...any) (bool, error)
    60  	Distinct(...string) *xorm.Session
    61  	Query(...any) ([]map[string][]byte, error)
    62  	Cols(...string) *xorm.Session
    63  	Context(ctx context.Context) *xorm.Session
    64  	Ping() error
    65  }
    66  
    67  // TableInfo returns table's information via an object
    68  func TableInfo(v any) (*schemas.Table, error) {
    69  	return x.TableInfo(v)
    70  }
    71  
    72  // DumpTables dump tables information
    73  func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
    74  	return x.DumpTables(tables, w, tp...)
    75  }
    76  
    77  // RegisterModel registers model, if initfunc provided, it will be invoked after data model sync
    78  func RegisterModel(bean any, initFunc ...func() error) {
    79  	tables = append(tables, bean)
    80  	if len(initFuncs) > 0 && initFunc[0] != nil {
    81  		initFuncs = append(initFuncs, initFunc[0])
    82  	}
    83  }
    84  
    85  func init() {
    86  	gonicNames := []string{"SSL", "UID"}
    87  	for _, name := range gonicNames {
    88  		names.LintGonicMapper[name] = true
    89  	}
    90  }
    91  
    92  // newXORMEngine returns a new XORM engine from the configuration
    93  func newXORMEngine() (*xorm.Engine, error) {
    94  	connStr, err := setting.DBConnStr()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	var engine *xorm.Engine
   100  
   101  	if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 {
   102  		// OK whilst we sort out our schema issues - create a schema aware postgres
   103  		registerPostgresSchemaDriver()
   104  		engine, err = xorm.NewEngine("postgresschema", connStr)
   105  	} else {
   106  		engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr)
   107  	}
   108  
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	if setting.Database.Type == "mysql" {
   113  		engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"})
   114  	} else if setting.Database.Type == "mssql" {
   115  		engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"})
   116  	}
   117  	engine.SetSchema(setting.Database.Schema)
   118  	return engine, nil
   119  }
   120  
   121  // SyncAllTables sync the schemas of all tables, is required by unit test code
   122  func SyncAllTables() error {
   123  	_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
   124  		WarnIfDatabaseColumnMissed: true,
   125  	}, tables...)
   126  	return err
   127  }
   128  
   129  // InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
   130  func InitEngine(ctx context.Context) error {
   131  	xormEngine, err := newXORMEngine()
   132  	if err != nil {
   133  		return fmt.Errorf("failed to connect to database: %w", err)
   134  	}
   135  
   136  	xormEngine.SetMapper(names.GonicMapper{})
   137  	// WARNING: for serv command, MUST remove the output to os.stdout,
   138  	// so use log file to instead print to stdout.
   139  	xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
   140  	xormEngine.ShowSQL(setting.Database.LogSQL)
   141  	xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
   142  	xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
   143  	xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
   144  	xormEngine.SetDefaultContext(ctx)
   145  
   146  	SetDefaultEngine(ctx, xormEngine)
   147  	return nil
   148  }
   149  
   150  // SetDefaultEngine sets the default engine for db
   151  func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
   152  	x = eng
   153  	DefaultContext = &Context{
   154  		Context: ctx,
   155  		e:       x,
   156  	}
   157  }
   158  
   159  // UnsetDefaultEngine closes and unsets the default engine
   160  // We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now,
   161  // there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close
   162  // Global database engine related functions are all racy and there is no graceful close right now.
   163  func UnsetDefaultEngine() {
   164  	if x != nil {
   165  		_ = x.Close()
   166  		x = nil
   167  	}
   168  	DefaultContext = nil
   169  }
   170  
   171  // InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext
   172  // This function must never call .Sync() if the provided migration function fails.
   173  // When called from the "doctor" command, the migration function is a version check
   174  // that prevents the doctor from fixing anything in the database if the migration level
   175  // is different from the expected value.
   176  func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
   177  	if err = InitEngine(ctx); err != nil {
   178  		return err
   179  	}
   180  
   181  	if err = x.Ping(); err != nil {
   182  		return err
   183  	}
   184  
   185  	// We have to run migrateFunc here in case the user is re-running installation on a previously created DB.
   186  	// If we do not then table schemas will be changed and there will be conflicts when the migrations run properly.
   187  	//
   188  	// Installation should only be being re-run if users want to recover an old database.
   189  	// However, we should think carefully about should we support re-install on an installed instance,
   190  	// as there may be other problems due to secret reinitialization.
   191  	if err = migrateFunc(x); err != nil {
   192  		return fmt.Errorf("migrate: %w", err)
   193  	}
   194  
   195  	if err = SyncAllTables(); err != nil {
   196  		return fmt.Errorf("sync database struct error: %w", err)
   197  	}
   198  
   199  	for _, initFunc := range initFuncs {
   200  		if err := initFunc(); err != nil {
   201  			return fmt.Errorf("initFunc failed: %w", err)
   202  		}
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // NamesToBean return a list of beans or an error
   209  func NamesToBean(names ...string) ([]any, error) {
   210  	beans := []any{}
   211  	if len(names) == 0 {
   212  		beans = append(beans, tables...)
   213  		return beans, nil
   214  	}
   215  	// Need to map provided names to beans...
   216  	beanMap := make(map[string]any)
   217  	for _, bean := range tables {
   218  
   219  		beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean
   220  		beanMap[strings.ToLower(x.TableName(bean))] = bean
   221  		beanMap[strings.ToLower(x.TableName(bean, true))] = bean
   222  	}
   223  
   224  	gotBean := make(map[any]bool)
   225  	for _, name := range names {
   226  		bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
   227  		if !ok {
   228  			return nil, fmt.Errorf("no table found that matches: %s", name)
   229  		}
   230  		if !gotBean[bean] {
   231  			beans = append(beans, bean)
   232  			gotBean[bean] = true
   233  		}
   234  	}
   235  	return beans, nil
   236  }
   237  
   238  // DumpDatabase dumps all data from database according the special database SQL syntax to file system.
   239  func DumpDatabase(filePath, dbType string) error {
   240  	var tbs []*schemas.Table
   241  	for _, t := range tables {
   242  		t, err := x.TableInfo(t)
   243  		if err != nil {
   244  			return err
   245  		}
   246  		tbs = append(tbs, t)
   247  	}
   248  
   249  	type Version struct {
   250  		ID      int64 `xorm:"pk autoincr"`
   251  		Version int64
   252  	}
   253  	t, err := x.TableInfo(&Version{})
   254  	if err != nil {
   255  		return err
   256  	}
   257  	tbs = append(tbs, t)
   258  
   259  	if len(dbType) > 0 {
   260  		return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType))
   261  	}
   262  	return x.DumpTablesToFile(tbs, filePath)
   263  }
   264  
   265  // MaxBatchInsertSize returns the table's max batch insert size
   266  func MaxBatchInsertSize(bean any) int {
   267  	t, err := x.TableInfo(bean)
   268  	if err != nil {
   269  		return 50
   270  	}
   271  	return 999 / len(t.ColumnsSeq())
   272  }
   273  
   274  // IsTableNotEmpty returns true if table has at least one record
   275  func IsTableNotEmpty(tableName string) (bool, error) {
   276  	return x.Table(tableName).Exist()
   277  }
   278  
   279  // DeleteAllRecords will delete all the records of this table
   280  func DeleteAllRecords(tableName string) error {
   281  	_, err := x.Exec(fmt.Sprintf("DELETE FROM %s", tableName))
   282  	return err
   283  }
   284  
   285  // GetMaxID will return max id of the table
   286  func GetMaxID(beanOrTableName any) (maxID int64, err error) {
   287  	_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
   288  	return maxID, err
   289  }
   290  
   291  func SetLogSQL(ctx context.Context, on bool) {
   292  	e := GetEngine(ctx)
   293  	if x, ok := e.(*xorm.Engine); ok {
   294  		x.ShowSQL(on)
   295  	} else if sess, ok := e.(*xorm.Session); ok {
   296  		sess.Engine().ShowSQL(on)
   297  	}
   298  }