2014-02-12 12:49:46 -05:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-06-20 01:06:01 -04:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-02-12 12:49:46 -05:00
2021-09-19 19:49:59 +08:00
package db
2014-02-13 23:23:23 +08:00
2014-02-18 17:48:02 -05:00
import (
2019-12-15 09:51:28 +00:00
"context"
2014-10-19 01:35:24 -04:00
"database/sql"
2024-02-12 23:11:50 +01:00
"errors"
2014-02-18 17:48:02 -05:00
"fmt"
2021-09-19 19:49:59 +08:00
"io"
2020-09-06 22:52:01 +01:00
"reflect"
"strings"
2023-08-18 04:39:23 +02:00
"time"
2014-02-18 17:48:02 -05:00
2023-08-18 04:39:23 +02:00
"code.gitea.io/gitea/modules/log"
2017-10-26 23:10:54 -07:00
"code.gitea.io/gitea/modules/setting"
2019-10-17 17:26:49 +08:00
"xorm.io/xorm"
2023-08-18 04:39:23 +02:00
"xorm.io/xorm/contexts"
2020-03-22 23:12:55 +08:00
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
2016-11-26 01:20:18 +01:00
2024-04-04 18:02:24 +02:00
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver
2014-02-18 17:48:02 -05:00
)
2014-02-13 23:23:23 +08:00
2021-09-19 19:49:59 +08:00
var (
x * xorm . Engine
2023-07-04 20:36:08 +02:00
tables [ ] any
2021-09-19 19:49:59 +08:00
initFuncs [ ] func ( ) error
)
2014-10-19 01:35:24 -04:00
// Engine represents a xorm engine or session.
type Engine interface {
2023-07-04 20:36:08 +02:00
Table ( tableNameOrBean any ) * xorm . Session
Count ( ... any ) ( int64 , error )
Decr ( column string , arg ... any ) * xorm . Session
Delete ( ... any ) ( int64 , error )
Truncate ( ... any ) ( int64 , error )
Exec ( ... any ) ( sql . Result , error )
Find ( any , ... any ) error
Get ( beans ... any ) ( bool , error )
ID ( any ) * xorm . Session
In ( string , ... any ) * xorm . Session
Incr ( column string , arg ... any ) * xorm . Session
Insert ( ... any ) ( int64 , error )
Iterate ( any , xorm . IterFunc ) error
2023-08-08 23:55:25 +02:00
IsTableExist ( any ) ( bool , error )
2023-07-04 20:36:08 +02:00
Join ( joinOperator string , tablename , condition any , args ... any ) * xorm . Session
SQL ( any , ... any ) * xorm . Session
Where ( any , ... any ) * xorm . Session
2019-06-12 21:41:28 +02:00
Asc ( colNames ... string ) * xorm . Session
2020-08-17 04:07:38 +01:00
Desc ( colNames ... string ) * xorm . Session
2020-01-24 19:00:29 +00:00
Limit ( limit int , start ... int ) * xorm . Session
2021-11-21 23:41:00 +08:00
NoAutoTime ( ) * xorm . Session
2023-07-04 20:36:08 +02:00
SumInt ( bean any , columnName string ) ( res int64 , err error )
2023-08-13 21:17:21 +02:00
Sync ( ... any ) error
2021-09-19 19:49:59 +08:00
Select ( string ) * xorm . Session
2023-07-04 20:36:08 +02:00
NotIn ( string , ... any ) * xorm . Session
OrderBy ( any , ... any ) * xorm . Session
Exist ( ... any ) ( bool , error )
2021-09-19 19:49:59 +08:00
Distinct ( ... string ) * xorm . Session
2023-07-04 20:36:08 +02:00
Query ( ... any ) ( [ ] map [ string ] [ ] byte , error )
2021-09-19 19:49:59 +08:00
Cols ( ... string ) * xorm . Session
2022-01-19 23:26:57 +00:00
Context ( ctx context . Context ) * xorm . Session
2022-01-27 10:30:51 +02:00
Ping ( ) error
2014-10-19 01:35:24 -04:00
}
2021-09-19 19:49:59 +08:00
// TableInfo returns table's information via an object
2023-07-04 20:36:08 +02:00
func TableInfo ( v any ) ( * schemas . Table , error ) {
2021-09-19 19:49:59 +08:00
return x . TableInfo ( v )
}
2020-02-15 07:51:25 -03:00
2021-09-19 19:49:59 +08:00
// DumpTables dump tables information
func DumpTables ( tables [ ] * schemas . Table , w io . Writer , tp ... schemas . DBType ) error {
return x . DumpTables ( tables , w , tp ... )
}
2016-11-26 01:20:18 +01:00
2021-09-19 19:49:59 +08:00
// RegisterModel registers model, if initfunc provided, it will be invoked after data model sync
2023-07-04 20:36:08 +02:00
func RegisterModel ( bean any , initFunc ... func ( ) error ) {
2021-09-19 19:49:59 +08:00
tables = append ( tables , bean )
if len ( initFuncs ) > 0 && initFunc [ 0 ] != nil {
initFuncs = append ( initFuncs , initFunc [ 0 ] )
}
}
2014-03-21 01:48:10 -04:00
2014-04-05 22:46:32 +08:00
func init ( ) {
2016-11-26 01:20:18 +01:00
gonicNames := [ ] string { "SSL" , "UID" }
2015-08-27 23:06:14 +08:00
for _ , name := range gonicNames {
2020-03-22 23:12:55 +08:00
names . LintGonicMapper [ name ] = true
2015-08-27 23:06:14 +08:00
}
2014-04-05 22:46:32 +08:00
}
2021-12-01 15:50:01 +08:00
// newXORMEngine returns a new XORM engine from the configuration
func newXORMEngine ( ) ( * xorm . Engine , error ) {
2019-08-24 17:24:45 +08:00
connStr , err := setting . DBConnStr ( )
if err != nil {
return nil , err
2014-03-30 10:47:08 -04:00
}
2017-02-20 16:11:13 +08:00
2021-01-02 02:07:43 +00:00
var engine * xorm . Engine
2023-03-07 18:51:06 +08:00
if setting . Database . Type . IsPostgreSQL ( ) && len ( setting . Database . Schema ) > 0 {
2021-01-02 02:07:43 +00:00
// OK whilst we sort out our schema issues - create a schema aware postgres
registerPostgresSchemaDriver ( )
engine , err = xorm . NewEngine ( "postgresschema" , connStr )
} else {
2023-03-07 18:51:06 +08:00
engine , err = xorm . NewEngine ( setting . Database . Type . String ( ) , connStr )
2021-01-02 02:07:43 +00:00
}
2020-01-20 12:45:14 -03:00
if err != nil {
return nil , err
}
2024-04-04 18:02:24 +02:00
if setting . Database . Type . IsMySQL ( ) {
2020-03-27 10:44:05 +02:00
engine . Dialect ( ) . SetParams ( map [ string ] string { "rowFormat" : "DYNAMIC" } )
}
2020-01-20 12:45:14 -03:00
engine . SetSchema ( setting . Database . Schema )
return engine , nil
2014-09-04 17:19:26 +02:00
}
2022-01-20 18:46:10 +01:00
// SyncAllTables sync the schemas of all tables, is required by unit test code
2021-11-12 22:36:47 +08:00
func SyncAllTables ( ) error {
2023-06-24 16:20:08 +08:00
_ , err := x . StoreEngine ( "InnoDB" ) . SyncWithOptions ( xorm . SyncOptions {
WarnIfDatabaseColumnMissed : true ,
} , tables ... )
return err
2021-06-14 10:22:55 +08:00
}
2021-12-01 15:50:01 +08:00
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
func InitEngine ( ctx context . Context ) error {
xormEngine , err := newXORMEngine ( )
2014-02-18 17:48:02 -05:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "failed to connect to database: %w" , err )
2014-02-18 17:48:02 -05:00
}
2021-12-01 15:50:01 +08:00
xormEngine . SetMapper ( names . GonicMapper { } )
2014-12-06 20:22:48 -05:00
// WARNING: for serv command, MUST remove the output to os.stdout,
2014-03-20 16:04:56 -04:00
// so use log file to instead print to stdout.
2021-12-01 15:50:01 +08:00
xormEngine . SetLogger ( NewXORMLogger ( setting . Database . LogSQL ) )
xormEngine . ShowSQL ( setting . Database . LogSQL )
xormEngine . SetMaxOpenConns ( setting . Database . MaxOpenConns )
xormEngine . SetMaxIdleConns ( setting . Database . MaxIdleConns )
xormEngine . SetConnMaxLifetime ( setting . Database . ConnMaxLifetime )
2024-02-21 12:17:16 +00:00
xormEngine . SetConnMaxIdleTime ( setting . Database . ConnMaxIdleTime )
2021-12-01 15:50:01 +08:00
xormEngine . SetDefaultContext ( ctx )
2024-01-24 16:55:34 +01:00
if setting . Database . SlowQueryThreshold > 0 {
2023-08-18 04:39:23 +02:00
xormEngine . AddHook ( & SlowQueryHook {
2024-01-24 16:55:34 +01:00
Treshold : setting . Database . SlowQueryThreshold ,
2023-08-18 04:39:23 +02:00
Logger : log . GetLogger ( "xorm" ) ,
} )
}
2024-04-02 13:34:17 +02:00
errorLogger := log . GetLogger ( "xorm" )
if setting . IsInTesting {
errorLogger = log . GetLogger ( log . DEFAULT )
}
2024-01-14 00:01:39 +01:00
xormEngine . AddHook ( & ErrorQueryHook {
2024-04-02 13:34:17 +02:00
Logger : errorLogger ,
2024-01-14 00:01:39 +01:00
} )
2023-08-18 04:39:23 +02:00
2021-12-01 15:50:01 +08:00
SetDefaultEngine ( ctx , xormEngine )
return nil
}
2021-11-07 11:11:27 +08:00
2021-12-01 15:50:01 +08:00
// SetDefaultEngine sets the default engine for db
func SetDefaultEngine ( ctx context . Context , eng * xorm . Engine ) {
x = eng
2021-11-07 11:11:27 +08:00
DefaultContext = & Context {
Context : ctx ,
e : x ,
}
2014-02-18 17:48:02 -05:00
}
2021-12-01 15:50:01 +08:00
// UnsetDefaultEngine closes and unsets the default engine
// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now,
// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close
// Global database engine related functions are all racy and there is no graceful close right now.
func UnsetDefaultEngine ( ) {
if x != nil {
_ = x . Close ( )
x = nil
2021-11-16 16:53:21 +08:00
}
2021-12-01 15:50:01 +08:00
DefaultContext = nil
2021-11-16 16:53:21 +08:00
}
2021-12-01 15:50:01 +08:00
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext
2023-08-13 21:17:21 +02:00
// This function must never call .Sync() if the provided migration function fails.
2020-05-29 15:24:15 +02:00
// When called from the "doctor" command, the migration function is a version check
// that prevents the doctor from fixing anything in the database if the migration level
// is different from the expected value.
2021-10-30 22:32:11 +08:00
func InitEngineWithMigration ( ctx context . Context , migrateFunc func ( * xorm . Engine ) error ) ( err error ) {
2021-11-07 11:11:27 +08:00
if err = InitEngine ( ctx ) ; err != nil {
2014-03-29 17:50:51 -04:00
return err
2014-04-05 22:46:32 +08:00
}
2015-01-22 14:49:52 +02:00
2016-11-24 22:30:36 +08:00
if err = x . Ping ( ) ; err != nil {
return err
}
2024-01-10 19:03:23 +08:00
preprocessDatabaseCollation ( x )
2021-11-07 11:11:27 +08:00
// We have to run migrateFunc here in case the user is re-running installation on a previously created DB.
// If we do not then table schemas will be changed and there will be conflicts when the migrations run properly.
//
// Installation should only be being re-run if users want to recover an old database.
// However, we should think carefully about should we support re-install on an installed instance,
// as there may be other problems due to secret reinitialization.
2017-07-02 16:50:57 +03:00
if err = migrateFunc ( x ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "migrate: %w" , err )
2015-01-22 14:49:52 +02:00
}
2021-11-12 22:36:47 +08:00
if err = SyncAllTables ( ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "sync database struct error: %w" , err )
2014-02-19 17:50:53 +08:00
}
2015-01-23 09:54:16 +02:00
2021-09-19 19:49:59 +08:00
for _ , initFunc := range initFuncs {
if err := initFunc ( ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "initFunc failed: %w" , err )
2021-08-17 19:30:42 +01:00
}
}
2014-03-29 17:50:51 -04:00
return nil
2014-02-18 17:48:02 -05:00
}
2014-03-20 16:04:56 -04:00
2020-09-06 22:52:01 +01:00
// NamesToBean return a list of beans or an error
2023-07-04 20:36:08 +02:00
func NamesToBean ( names ... string ) ( [ ] any , error ) {
beans := [ ] any { }
2020-09-06 22:52:01 +01:00
if len ( names ) == 0 {
beans = append ( beans , tables ... )
return beans , nil
}
// Need to map provided names to beans...
2023-07-04 20:36:08 +02:00
beanMap := make ( map [ string ] any )
2020-09-06 22:52:01 +01:00
for _ , bean := range tables {
beanMap [ strings . ToLower ( reflect . Indirect ( reflect . ValueOf ( bean ) ) . Type ( ) . Name ( ) ) ] = bean
beanMap [ strings . ToLower ( x . TableName ( bean ) ) ] = bean
beanMap [ strings . ToLower ( x . TableName ( bean , true ) ) ] = bean
}
2023-07-04 20:36:08 +02:00
gotBean := make ( map [ any ] bool )
2020-09-06 22:52:01 +01:00
for _ , name := range names {
bean , ok := beanMap [ strings . ToLower ( strings . TrimSpace ( name ) ) ]
if ! ok {
2022-10-18 06:50:37 +01:00
return nil , fmt . Errorf ( "no table found that matches: %s" , name )
2020-09-06 22:52:01 +01:00
}
if ! gotBean [ bean ] {
beans = append ( beans , bean )
gotBean [ bean ] = true
}
}
return beans , nil
}
2017-01-03 16:20:28 +08:00
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
2021-03-15 02:52:12 +08:00
func DumpDatabase ( filePath , dbType string ) error {
2020-03-22 23:12:55 +08:00
var tbs [ ] * schemas . Table
2017-01-03 16:20:28 +08:00
for _ , t := range tables {
2020-03-22 23:12:55 +08:00
t , err := x . TableInfo ( t )
if err != nil {
return err
}
tbs = append ( tbs , t )
2017-01-03 16:20:28 +08:00
}
2020-09-08 00:27:17 +02:00
type Version struct {
ID int64 ` xorm:"pk autoincr" `
Version int64
}
2021-05-07 00:17:43 +01:00
t , err := x . TableInfo ( & Version { } )
2020-09-08 00:27:17 +02:00
if err != nil {
return err
}
tbs = append ( tbs , t )
2017-01-03 16:20:28 +08:00
if len ( dbType ) > 0 {
2020-03-22 23:12:55 +08:00
return x . DumpTablesToFile ( tbs , filePath , schemas . DBType ( dbType ) )
2017-01-03 16:20:28 +08:00
}
return x . DumpTablesToFile ( tbs , filePath )
2014-05-05 00:55:17 -04:00
}
2019-07-07 03:24:50 +08:00
// MaxBatchInsertSize returns the table's max batch insert size
2023-07-04 20:36:08 +02:00
func MaxBatchInsertSize ( bean any ) int {
2020-03-22 23:12:55 +08:00
t , err := x . TableInfo ( bean )
if err != nil {
return 50
}
2019-07-07 03:24:50 +08:00
return 999 / len ( t . ColumnsSeq ( ) )
}
2019-09-06 10:20:09 +08:00
2019-12-09 03:15:35 +08:00
// IsTableNotEmpty returns true if table has at least one record
func IsTableNotEmpty ( tableName string ) ( bool , error ) {
return x . Table ( tableName ) . Exist ( )
}
// DeleteAllRecords will delete all the records of this table
func DeleteAllRecords ( tableName string ) error {
_ , err := x . Exec ( fmt . Sprintf ( "DELETE FROM %s" , tableName ) )
return err
}
// GetMaxID will return max id of the table
2023-07-04 20:36:08 +02:00
func GetMaxID ( beanOrTableName any ) ( maxID int64 , err error ) {
2019-12-09 03:15:35 +08:00
_ , err = x . Select ( "MAX(id)" ) . Table ( beanOrTableName ) . Get ( & maxID )
2022-06-20 12:02:49 +02:00
return maxID , err
2019-12-09 03:15:35 +08:00
}
2022-06-24 11:49:47 +01:00
func SetLogSQL ( ctx context . Context , on bool ) {
e := GetEngine ( ctx )
if x , ok := e . ( * xorm . Engine ) ; ok {
x . ShowSQL ( on )
} else if sess , ok := e . ( * xorm . Session ) ; ok {
sess . Engine ( ) . ShowSQL ( on )
}
}
2023-08-18 04:39:23 +02:00
type SlowQueryHook struct {
Treshold time . Duration
Logger log . Logger
}
var _ contexts . Hook = & SlowQueryHook { }
func ( SlowQueryHook ) BeforeProcess ( c * contexts . ContextHook ) ( context . Context , error ) {
return c . Ctx , nil
}
func ( h * SlowQueryHook ) AfterProcess ( c * contexts . ContextHook ) error {
if c . ExecuteTime >= h . Treshold {
h . Logger . Log ( 8 , log . WARN , "[Slow SQL Query] %s %v - %v" , c . SQL , c . Args , c . ExecuteTime )
}
return nil
}
2024-01-14 00:01:39 +01:00
type ErrorQueryHook struct {
Logger log . Logger
}
var _ contexts . Hook = & ErrorQueryHook { }
func ( ErrorQueryHook ) BeforeProcess ( c * contexts . ContextHook ) ( context . Context , error ) {
return c . Ctx , nil
}
func ( h * ErrorQueryHook ) AfterProcess ( c * contexts . ContextHook ) error {
2024-02-12 23:11:50 +01:00
if c . Err != nil && ! errors . Is ( c . Err , context . Canceled ) {
2024-01-14 00:01:39 +01:00
h . Logger . Log ( 8 , log . ERROR , "[Error SQL Query] %s %v - %v" , c . SQL , c . Args , c . Err )
}
return nil
}