diff options
Diffstat (limited to 'models/db')
-rw-r--r-- | models/db/context.go | 16 | ||||
-rw-r--r-- | models/db/context_test.go | 2 | ||||
-rwxr-xr-x | models/db/engine.go | 2 | ||||
-rw-r--r-- | models/db/engine_hook.go | 25 | ||||
-rw-r--r-- | models/db/engine_init.go | 11 | ||||
-rw-r--r-- | models/db/engine_test.go | 2 | ||||
-rw-r--r-- | models/db/error.go | 2 | ||||
-rw-r--r-- | models/db/list_test.go | 2 | ||||
-rw-r--r-- | models/db/log.go | 2 | ||||
-rw-r--r-- | models/db/name.go | 11 | ||||
-rw-r--r-- | models/db/search.go | 4 | ||||
-rw-r--r-- | models/db/sql_postgres_with_schema.go | 4 |
12 files changed, 51 insertions, 32 deletions
diff --git a/models/db/context.go b/models/db/context.go index 51627712b1..ad99ada8c8 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -67,7 +67,7 @@ func contextSafetyCheck(e Engine) { _ = e.SQL("SELECT 1").Iterate(&m{}, func(int, any) error { callers := make([]uintptr, 32) callerNum := runtime.Callers(1, callers) - for i := 0; i < callerNum; i++ { + for i := range callerNum { if funcName := runtime.FuncForPC(callers[i]).Name(); funcName == "xorm.io/xorm.(*Session).Iterate" { contextSafetyDeniedFuncPCs = append(contextSafetyDeniedFuncPCs, callers[i]) } @@ -82,7 +82,7 @@ func contextSafetyCheck(e Engine) { // it should be very fast: xxxx ns/op callers := make([]uintptr, 32) callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine - for i := 0; i < callerNum; i++ { + for i := range callerNum { if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) { panic(errors.New("using database context in an iterator would cause corrupted results")) } @@ -178,6 +178,15 @@ func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error return txWithNoCheck(parentCtx, f) } +// WithTx2 is similar to WithTx, but it has two return values: result and error. +func WithTx2[T any](parentCtx context.Context, f func(ctx context.Context) (T, error)) (ret T, errRet error) { + errRet = WithTx(parentCtx, func(ctx context.Context) (errInner error) { + ret, errInner = f(ctx) + return errInner + }) + return ret, errRet +} + func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error { sess := xormEngine.NewSession() defer sess.Close() @@ -289,6 +298,9 @@ func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([ // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one // Timestamps of the entities won't be updated func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error { + if len(ids) == 0 { + return nil + } _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean) return err } diff --git a/models/db/context_test.go b/models/db/context_test.go index e8c6b74d93..a6bd11d2ae 100644 --- a/models/db/context_test.go +++ b/models/db/context_test.go @@ -118,7 +118,7 @@ func TestContextSafety(t *testing.T) { }) return nil }) - assert.EqualValues(t, testCount, actualCount) + assert.Equal(t, testCount, actualCount) // deny the bad usages assert.PanicsWithError(t, "using database context in an iterator would cause corrupted results", func() { diff --git a/models/db/engine.go b/models/db/engine.go index 91015f7038..ba287d58f0 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -127,7 +127,7 @@ func IsTableNotEmpty(beanOrTableName any) (bool, error) { // DeleteAllRecords will delete all the records of this table func DeleteAllRecords(tableName string) error { - _, err := xormEngine.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) + _, err := xormEngine.Exec("DELETE FROM " + tableName) return err } diff --git a/models/db/engine_hook.go b/models/db/engine_hook.go index b4c543c3dd..8709a2c2a1 100644 --- a/models/db/engine_hook.go +++ b/models/db/engine_hook.go @@ -7,28 +7,41 @@ import ( "context" "time" + "code.gitea.io/gitea/modules/gtprof" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "xorm.io/xorm/contexts" ) -type SlowQueryHook struct { +type EngineHook struct { Threshold time.Duration Logger log.Logger } -var _ contexts.Hook = (*SlowQueryHook)(nil) +var _ contexts.Hook = (*EngineHook)(nil) -func (*SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { - return c.Ctx, nil +func (*EngineHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { + ctx, _ := gtprof.GetTracer().Start(c.Ctx, gtprof.TraceSpanDatabase) + return ctx, nil } -func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error { +func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error { + span := gtprof.GetContextSpan(c.Ctx) + if span != nil { + // Do not record SQL parameters here: + // * It shouldn't expose the parameters because they contain sensitive information, end users need to report the trace details safely. + // * Some parameters contain quite long texts, waste memory and are difficult to display. + span.SetAttributeString(gtprof.TraceAttrDbSQL, c.SQL) + span.End() + } else { + setting.PanicInDevOrTesting("span in database engine hook is nil") + } if c.ExecuteTime >= h.Threshold { // 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function // is being displayed (the function that ultimately wants to execute the query in the code) // instead of the function of the slow query hook being called. - h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) + h.Logger.Log(8, &log.Event{Level: log.WARN}, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) } return nil } diff --git a/models/db/engine_init.go b/models/db/engine_init.go index da85018957..bb02aff274 100644 --- a/models/db/engine_init.go +++ b/models/db/engine_init.go @@ -42,9 +42,10 @@ func newXORMEngine() (*xorm.Engine, error) { if err != nil { return nil, err } - if setting.Database.Type == "mysql" { + switch setting.Database.Type { + case "mysql": engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"}) - } else if setting.Database.Type == "mssql" { + case "mssql": engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"}) } engine.SetSchema(setting.Database.Schema) @@ -72,7 +73,7 @@ func InitEngine(ctx context.Context) error { xe.SetDefaultContext(ctx) if setting.Database.SlowQueryThreshold > 0 { - xe.AddHook(&SlowQueryHook{ + xe.AddHook(&EngineHook{ Threshold: setting.Database.SlowQueryThreshold, Logger: log.GetLogger("xorm"), }) @@ -105,7 +106,7 @@ func UnsetDefaultEngine() { // 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. -func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { +func InitEngineWithMigration(ctx context.Context, migrateFunc func(context.Context, *xorm.Engine) error) (err error) { if err = InitEngine(ctx); err != nil { return err } @@ -122,7 +123,7 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) // 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. - if err = migrateFunc(xormEngine); err != nil { + if err = migrateFunc(ctx, xormEngine); err != nil { return fmt.Errorf("migrate: %w", err) } diff --git a/models/db/engine_test.go b/models/db/engine_test.go index 10a1a33ff0..a236f83735 100644 --- a/models/db/engine_test.go +++ b/models/db/engine_test.go @@ -52,7 +52,7 @@ func TestDeleteOrphanedObjects(t *testing.T) { countAfter, err := db.GetEngine(db.DefaultContext).Count(&issues_model.PullRequest{}) assert.NoError(t, err) - assert.EqualValues(t, countBefore, countAfter) + assert.Equal(t, countBefore, countAfter) } func TestPrimaryKeys(t *testing.T) { diff --git a/models/db/error.go b/models/db/error.go index 665e970e17..d47c7adac4 100644 --- a/models/db/error.go +++ b/models/db/error.go @@ -65,7 +65,7 @@ func (err ErrNotExist) Error() string { if err.ID != 0 { return fmt.Sprintf("%s does not exist [id: %d]", name, err.ID) } - return fmt.Sprintf("%s does not exist", name) + return name + " does not exist" } // Unwrap unwraps this as a ErrNotExist err diff --git a/models/db/list_test.go b/models/db/list_test.go index 45194611f8..170473a968 100644 --- a/models/db/list_test.go +++ b/models/db/list_test.go @@ -47,6 +47,6 @@ func TestFind(t *testing.T) { repoUnits, newCnt, err := db.FindAndCount[repo_model.RepoUnit](db.DefaultContext, opts) assert.NoError(t, err) - assert.EqualValues(t, cnt, newCnt) + assert.Equal(t, cnt, newCnt) assert.Len(t, repoUnits, repoUnitCount) } diff --git a/models/db/log.go b/models/db/log.go index 307788ea2e..a9df6f541d 100644 --- a/models/db/log.go +++ b/models/db/log.go @@ -29,7 +29,7 @@ const stackLevel = 8 // Log a message with defined skip and at logging level func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...any) { - l.logger.Log(skip+1, level, format, v...) + l.logger.Log(skip+1, &log.Event{Level: level}, format, v...) } // Debug show debug log diff --git a/models/db/name.go b/models/db/name.go index 5f98edbb28..48c7fdbce5 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -5,14 +5,13 @@ package db import ( "fmt" + "slices" "strings" "unicode/utf8" "code.gitea.io/gitea/modules/util" ) -var ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument} - // ErrNameReserved represents a "reserved name" error. type ErrNameReserved struct { Name string @@ -79,13 +78,11 @@ func (err ErrNameCharsNotAllowed) Unwrap() error { func IsUsableName(reservedNames, reservedPatterns []string, name string) error { name = strings.TrimSpace(strings.ToLower(name)) if utf8.RuneCountInString(name) == 0 { - return ErrNameEmpty + return util.NewInvalidArgumentErrorf("name is empty") } - for i := range reservedNames { - if name == reservedNames[i] { - return ErrNameReserved{name} - } + if slices.Contains(reservedNames, name) { + return ErrNameReserved{name} } for _, pat := range reservedPatterns { diff --git a/models/db/search.go b/models/db/search.go index e0a1b6bde9..44d54f21fc 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -29,7 +29,3 @@ const ( // NoConditionID means a condition to filter the records which don't match any id. // eg: "milestone_id=-1" means "find the items without any milestone. const NoConditionID int64 = -1 - -// NonExistingID means a condition to match no result (eg: a non-existing user) -// It doesn't use -1 or -2 because they are used as builtin users. -const NonExistingID int64 = -1000000 diff --git a/models/db/sql_postgres_with_schema.go b/models/db/sql_postgres_with_schema.go index 64b61b2ef3..812fe4a6a6 100644 --- a/models/db/sql_postgres_with_schema.go +++ b/models/db/sql_postgres_with_schema.go @@ -39,7 +39,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) { // golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here // and in any case pq does not implement it - if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck + if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck // see above _, err := execer.Exec(`SELECT set_config( 'search_path', $1 || ',' || current_setting('search_path'), @@ -64,7 +64,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) { // driver.String.ConvertValue will never return err for string // golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here - _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck + _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck // see above if err != nil { _ = conn.Close() return nil, err |