gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
/
engine.go 
331 строка · 9.7 Кб
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
	"time"
15

16
	"code.gitea.io/gitea/modules/log"
17
	"code.gitea.io/gitea/modules/setting"
18

19
	"xorm.io/xorm"
20
	"xorm.io/xorm/contexts"
21
	"xorm.io/xorm/names"
22
	"xorm.io/xorm/schemas"
23

24
	_ "github.com/go-sql-driver/mysql"  // Needed for the MySQL driver
25
	_ "github.com/lib/pq"               // Needed for the Postgresql driver
26
	_ "github.com/microsoft/go-mssqldb" // Needed for the MSSQL driver
27
)
28

29
var (
30
	x         *xorm.Engine
31
	tables    []any
32
	initFuncs []func() error
33
)
34

35
// Engine represents a xorm engine or session.
36
type Engine interface {
37
	Table(tableNameOrBean any) *xorm.Session
38
	Count(...any) (int64, error)
39
	Decr(column string, arg ...any) *xorm.Session
40
	Delete(...any) (int64, error)
41
	Truncate(...any) (int64, error)
42
	Exec(...any) (sql.Result, error)
43
	Find(any, ...any) error
44
	Get(beans ...any) (bool, error)
45
	ID(any) *xorm.Session
46
	In(string, ...any) *xorm.Session
47
	Incr(column string, arg ...any) *xorm.Session
48
	Insert(...any) (int64, error)
49
	Iterate(any, xorm.IterFunc) error
50
	Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
51
	SQL(any, ...any) *xorm.Session
52
	Where(any, ...any) *xorm.Session
53
	Asc(colNames ...string) *xorm.Session
54
	Desc(colNames ...string) *xorm.Session
55
	Limit(limit int, start ...int) *xorm.Session
56
	NoAutoTime() *xorm.Session
57
	SumInt(bean any, columnName string) (res int64, err error)
58
	Sync(...any) error
59
	Select(string) *xorm.Session
60
	SetExpr(string, any) *xorm.Session
61
	NotIn(string, ...any) *xorm.Session
62
	OrderBy(any, ...any) *xorm.Session
63
	Exist(...any) (bool, error)
64
	Distinct(...string) *xorm.Session
65
	Query(...any) ([]map[string][]byte, error)
66
	Cols(...string) *xorm.Session
67
	Context(ctx context.Context) *xorm.Session
68
	Ping() error
69
}
70

71
// TableInfo returns table's information via an object
72
func TableInfo(v any) (*schemas.Table, error) {
73
	return x.TableInfo(v)
74
}
75

76
// DumpTables dump tables information
77
func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
78
	return x.DumpTables(tables, w, tp...)
79
}
80

81
// RegisterModel registers model, if initfunc provided, it will be invoked after data model sync
82
func RegisterModel(bean any, initFunc ...func() error) {
83
	tables = append(tables, bean)
84
	if len(initFuncs) > 0 && initFunc[0] != nil {
85
		initFuncs = append(initFuncs, initFunc[0])
86
	}
87
}
88

89
func init() {
90
	gonicNames := []string{"SSL", "UID"}
91
	for _, name := range gonicNames {
92
		names.LintGonicMapper[name] = true
93
	}
94
}
95

96
// newXORMEngine returns a new XORM engine from the configuration
97
func newXORMEngine() (*xorm.Engine, error) {
98
	connStr, err := setting.DBConnStr()
99
	if err != nil {
100
		return nil, err
101
	}
102

103
	var engine *xorm.Engine
104

105
	if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 {
106
		// OK whilst we sort out our schema issues - create a schema aware postgres
107
		registerPostgresSchemaDriver()
108
		engine, err = xorm.NewEngine("postgresschema", connStr)
109
	} else {
110
		engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr)
111
	}
112

113
	if err != nil {
114
		return nil, err
115
	}
116
	if setting.Database.Type == "mysql" {
117
		engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"})
118
	} else if setting.Database.Type == "mssql" {
119
		engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"})
120
	}
121
	engine.SetSchema(setting.Database.Schema)
122
	return engine, nil
123
}
124

125
// SyncAllTables sync the schemas of all tables, is required by unit test code
126
func SyncAllTables() error {
127
	_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
128
		WarnIfDatabaseColumnMissed: true,
129
	}, tables...)
130
	return err
131
}
132

133
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
134
func InitEngine(ctx context.Context) error {
135
	xormEngine, err := newXORMEngine()
136
	if err != nil {
137
		return fmt.Errorf("failed to connect to database: %w", err)
138
	}
139

140
	xormEngine.SetMapper(names.GonicMapper{})
141
	// WARNING: for serv command, MUST remove the output to os.stdout,
142
	// so use log file to instead print to stdout.
143
	xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
144
	xormEngine.ShowSQL(setting.Database.LogSQL)
145
	xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
146
	xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
147
	xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
148
	xormEngine.SetDefaultContext(ctx)
149

150
	if setting.Database.SlowQueryThreshold > 0 {
151
		xormEngine.AddHook(&SlowQueryHook{
152
			Threshold: setting.Database.SlowQueryThreshold,
153
			Logger:    log.GetLogger("xorm"),
154
		})
155
	}
156

157
	SetDefaultEngine(ctx, xormEngine)
158
	return nil
159
}
160

161
// SetDefaultEngine sets the default engine for db
162
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
163
	x = eng
164
	DefaultContext = &Context{
165
		Context: ctx,
166
		e:       x,
167
	}
168
}
169

170
// UnsetDefaultEngine closes and unsets the default engine
171
// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now,
172
// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close
173
// Global database engine related functions are all racy and there is no graceful close right now.
174
func UnsetDefaultEngine() {
175
	if x != nil {
176
		_ = x.Close()
177
		x = nil
178
	}
179
	DefaultContext = nil
180
}
181

182
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext
183
// This function must never call .Sync() if the provided migration function fails.
184
// When called from the "doctor" command, the migration function is a version check
185
// that prevents the doctor from fixing anything in the database if the migration level
186
// is different from the expected value.
187
func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
188
	if err = InitEngine(ctx); err != nil {
189
		return err
190
	}
191

192
	if err = x.Ping(); err != nil {
193
		return err
194
	}
195

196
	preprocessDatabaseCollation(x)
197

198
	// We have to run migrateFunc here in case the user is re-running installation on a previously created DB.
199
	// If we do not then table schemas will be changed and there will be conflicts when the migrations run properly.
200
	//
201
	// Installation should only be being re-run if users want to recover an old database.
202
	// However, we should think carefully about should we support re-install on an installed instance,
203
	// as there may be other problems due to secret reinitialization.
204
	if err = migrateFunc(x); err != nil {
205
		return fmt.Errorf("migrate: %w", err)
206
	}
207

208
	if err = SyncAllTables(); err != nil {
209
		return fmt.Errorf("sync database struct error: %w", err)
210
	}
211

212
	for _, initFunc := range initFuncs {
213
		if err := initFunc(); err != nil {
214
			return fmt.Errorf("initFunc failed: %w", err)
215
		}
216
	}
217

218
	return nil
219
}
220

221
// NamesToBean return a list of beans or an error
222
func NamesToBean(names ...string) ([]any, error) {
223
	beans := []any{}
224
	if len(names) == 0 {
225
		beans = append(beans, tables...)
226
		return beans, nil
227
	}
228
	// Need to map provided names to beans...
229
	beanMap := make(map[string]any)
230
	for _, bean := range tables {
231
		beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean
232
		beanMap[strings.ToLower(x.TableName(bean))] = bean
233
		beanMap[strings.ToLower(x.TableName(bean, true))] = bean
234
	}
235

236
	gotBean := make(map[any]bool)
237
	for _, name := range names {
238
		bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
239
		if !ok {
240
			return nil, fmt.Errorf("no table found that matches: %s", name)
241
		}
242
		if !gotBean[bean] {
243
			beans = append(beans, bean)
244
			gotBean[bean] = true
245
		}
246
	}
247
	return beans, nil
248
}
249

250
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
251
func DumpDatabase(filePath, dbType string) error {
252
	var tbs []*schemas.Table
253
	for _, t := range tables {
254
		t, err := x.TableInfo(t)
255
		if err != nil {
256
			return err
257
		}
258
		tbs = append(tbs, t)
259
	}
260

261
	type Version struct {
262
		ID      int64 `xorm:"pk autoincr"`
263
		Version int64
264
	}
265
	t, err := x.TableInfo(&Version{})
266
	if err != nil {
267
		return err
268
	}
269
	tbs = append(tbs, t)
270

271
	if len(dbType) > 0 {
272
		return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType))
273
	}
274
	return x.DumpTablesToFile(tbs, filePath)
275
}
276

277
// MaxBatchInsertSize returns the table's max batch insert size
278
func MaxBatchInsertSize(bean any) int {
279
	t, err := x.TableInfo(bean)
280
	if err != nil {
281
		return 50
282
	}
283
	return 999 / len(t.ColumnsSeq())
284
}
285

286
// IsTableNotEmpty returns true if table has at least one record
287
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
288
	return x.Table(beanOrTableName).Exist()
289
}
290

291
// DeleteAllRecords will delete all the records of this table
292
func DeleteAllRecords(tableName string) error {
293
	_, err := x.Exec(fmt.Sprintf("DELETE FROM %s", tableName))
294
	return err
295
}
296

297
// GetMaxID will return max id of the table
298
func GetMaxID(beanOrTableName any) (maxID int64, err error) {
299
	_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
300
	return maxID, err
301
}
302

303
func SetLogSQL(ctx context.Context, on bool) {
304
	e := GetEngine(ctx)
305
	if x, ok := e.(*xorm.Engine); ok {
306
		x.ShowSQL(on)
307
	} else if sess, ok := e.(*xorm.Session); ok {
308
		sess.Engine().ShowSQL(on)
309
	}
310
}
311

312
type SlowQueryHook struct {
313
	Threshold time.Duration
314
	Logger    log.Logger
315
}
316

317
var _ contexts.Hook = &SlowQueryHook{}
318

319
func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
320
	return c.Ctx, nil
321
}
322

323
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
324
	if c.ExecuteTime >= h.Threshold {
325
		// 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function
326
		// is being displayed (the function that ultimately wants to execute the query in the code)
327
		// instead of the function of the slow query hook being called.
328
		h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
329
	}
330
	return nil
331
}
332

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.