1// Copyright 2019 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package db
6
7import (
8	"context"
9	"database/sql"
10
11	"code.gitea.io/gitea/modules/setting"
12
13	"xorm.io/builder"
14)
15
16// DefaultContext is the default context to run xorm queries in
17// will be overwritten by Init with HammerContext
18var DefaultContext context.Context
19
20// contextKey is a value for use with context.WithValue.
21type contextKey struct {
22	name string
23}
24
25// EnginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
26var EnginedContextKey = &contextKey{"engined"}
27
28// Context represents a db context
29type Context struct {
30	context.Context
31	e Engine
32}
33
34// WithEngine returns a db.Context from a context.Context and db.Engine
35func WithEngine(ctx context.Context, e Engine) *Context {
36	return &Context{
37		Context: ctx,
38		e:       e,
39	}
40}
41
42// Engine returns db engine
43func (ctx *Context) Engine() Engine {
44	return ctx.e
45}
46
47// Value shadows Value for context.Context but allows us to get ourselves and an Engined object
48func (ctx *Context) Value(key interface{}) interface{} {
49	if key == EnginedContextKey {
50		return ctx
51	}
52	return ctx.Context.Value(key)
53}
54
55// Engined structs provide an Engine
56type Engined interface {
57	Engine() Engine
58}
59
60// GetEngine will get a db Engine from this context or return an Engine restricted to this context
61func GetEngine(ctx context.Context) Engine {
62	if engined, ok := ctx.(Engined); ok {
63		return engined.Engine()
64	}
65	enginedInterface := ctx.Value(EnginedContextKey)
66	if enginedInterface != nil {
67		return enginedInterface.(Engined).Engine()
68	}
69	return x.Context(ctx)
70}
71
72// Committer represents an interface to Commit or Close the Context
73type Committer interface {
74	Commit() error
75	Close() error
76}
77
78// TxContext represents a transaction Context
79func TxContext() (*Context, Committer, error) {
80	sess := x.NewSession()
81	if err := sess.Begin(); err != nil {
82		sess.Close()
83		return nil, nil, err
84	}
85
86	return &Context{
87		Context: DefaultContext,
88		e:       sess,
89	}, sess, nil
90}
91
92// WithContext represents executing database operations
93func WithContext(f func(ctx *Context) error) error {
94	return f(&Context{
95		Context: DefaultContext,
96		e:       x,
97	})
98}
99
100// WithTx represents executing database operations on a transaction
101func WithTx(f func(ctx context.Context) error) error {
102	sess := x.NewSession()
103	defer sess.Close()
104	if err := sess.Begin(); err != nil {
105		return err
106	}
107
108	if err := f(&Context{
109		Context: DefaultContext,
110		e:       sess,
111	}); err != nil {
112		return err
113	}
114
115	return sess.Commit()
116}
117
118// Iterate iterates the databases and doing something
119func Iterate(ctx context.Context, tableBean interface{}, cond builder.Cond, fun func(idx int, bean interface{}) error) error {
120	return GetEngine(ctx).Where(cond).
121		BufferSize(setting.Database.IterateBufferSize).
122		Iterate(tableBean, fun)
123}
124
125// Insert inserts records into database
126func Insert(ctx context.Context, beans ...interface{}) error {
127	_, err := GetEngine(ctx).Insert(beans...)
128	return err
129}
130
131// Exec executes a sql with args
132func Exec(ctx context.Context, sqlAndArgs ...interface{}) (sql.Result, error) {
133	return GetEngine(ctx).Exec(sqlAndArgs...)
134}
135
136// GetByBean filled empty fields of the bean according non-empty fields to query in database.
137func GetByBean(ctx context.Context, bean interface{}) (bool, error) {
138	return GetEngine(ctx).Get(bean)
139}
140
141// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
142func DeleteByBean(ctx context.Context, bean interface{}) (int64, error) {
143	return GetEngine(ctx).Delete(bean)
144}
145
146// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
147func CountByBean(ctx context.Context, bean interface{}) (int64, error) {
148	return GetEngine(ctx).Count(bean)
149}
150
151// TableName returns the table name according a bean object
152func TableName(bean interface{}) string {
153	return x.TableName(bean)
154}
155