2021-01-17 21:46:25 +08:00
|
|
|
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
2017-12-29 16:03:30 +08:00
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
|
|
// If a copy of the MIT was not distributed with this file,
|
2019-02-02 16:18:25 +08:00
|
|
|
// You can obtain one at https://github.com/gogf/gf.
|
2017-12-31 18:19:58 +08:00
|
|
|
|
2019-01-15 23:27:47 +08:00
|
|
|
// Package gdb provides ORM features for popular relationship databases.
|
2017-11-23 10:21:28 +08:00
|
|
|
package gdb
|
|
|
|
|
|
|
|
import (
|
2020-11-29 23:47:57 +08:00
|
|
|
"context"
|
2019-06-19 09:06:52 +08:00
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
2020-12-09 16:04:29 +08:00
|
|
|
"github.com/gogf/gf/errors/gerror"
|
2020-12-04 23:44:54 +08:00
|
|
|
"github.com/gogf/gf/os/gcmd"
|
2020-06-28 16:50:13 +08:00
|
|
|
"time"
|
|
|
|
|
2020-04-08 21:26:14 +08:00
|
|
|
"github.com/gogf/gf/container/gvar"
|
2020-03-19 13:38:42 +08:00
|
|
|
"github.com/gogf/gf/internal/intlog"
|
2019-06-29 18:17:33 +08:00
|
|
|
|
2019-08-26 19:39:04 +08:00
|
|
|
"github.com/gogf/gf/os/glog"
|
|
|
|
|
2019-07-29 21:01:19 +08:00
|
|
|
"github.com/gogf/gf/container/gmap"
|
|
|
|
"github.com/gogf/gf/container/gtype"
|
|
|
|
"github.com/gogf/gf/os/gcache"
|
|
|
|
"github.com/gogf/gf/util/grand"
|
2017-11-23 10:21:28 +08:00
|
|
|
)
|
|
|
|
|
2020-03-09 21:53:58 +08:00
|
|
|
// DB defines the interfaces for ORM operations.
|
2018-12-14 18:35:51 +08:00
|
|
|
type DB interface {
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
// Model creation.
|
|
|
|
// ===========================================================================
|
|
|
|
|
2020-07-11 09:53:16 +08:00
|
|
|
// The DB interface is designed not only for
|
2020-07-08 20:48:29 +08:00
|
|
|
// relational databases but also for NoSQL databases in the future. The name
|
|
|
|
// "Table" is not proper for that purpose any more.
|
|
|
|
Table(table ...string) *Model
|
|
|
|
Model(table ...string) *Model
|
|
|
|
Schema(schema string) *Schema
|
|
|
|
|
2019-12-23 23:14:54 +08:00
|
|
|
// Open creates a raw connection object for database with given node configuration.
|
|
|
|
// Note that it is not recommended using the this function manually.
|
2019-06-19 09:06:52 +08:00
|
|
|
Open(config *ConfigNode) (*sql.DB, error)
|
2018-12-15 15:50:39 +08:00
|
|
|
|
2020-11-29 23:47:57 +08:00
|
|
|
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
|
|
|
|
// of current DB object and with given context in it.
|
|
|
|
// Note that this returned DB object can be used only once, so do not assign it to
|
|
|
|
// a global or package variable for long using.
|
|
|
|
Ctx(ctx context.Context) DB
|
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2019-12-23 23:14:54 +08:00
|
|
|
// Query APIs.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-03-22 23:26:15 +08:00
|
|
|
Query(sql string, args ...interface{}) (*sql.Rows, error)
|
2018-12-14 18:35:51 +08:00
|
|
|
Exec(sql string, args ...interface{}) (sql.Result, error)
|
2021-01-25 21:17:32 +08:00
|
|
|
Prepare(sql string, execOnMaster ...bool) (*Stmt, error)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
// Common APIs for CURD.
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
|
|
|
|
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)
|
|
|
|
Replace(table string, data interface{}, batch ...int) (sql.Result, error)
|
|
|
|
Save(table string, data interface{}, batch ...int) (sql.Result, error)
|
|
|
|
|
|
|
|
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error)
|
|
|
|
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error)
|
|
|
|
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error)
|
|
|
|
|
|
|
|
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
|
|
|
|
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
|
|
|
|
|
|
|
|
// ===========================================================================
|
2019-12-23 23:14:54 +08:00
|
|
|
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-03-22 23:26:15 +08:00
|
|
|
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error)
|
|
|
|
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error)
|
|
|
|
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error)
|
2021-01-25 21:17:32 +08:00
|
|
|
DoPrepare(link Link, sql string) (*Stmt, error)
|
2020-03-09 21:53:58 +08:00
|
|
|
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
|
|
|
|
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
|
|
|
|
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
|
|
|
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
2018-12-15 15:50:39 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2019-12-23 23:14:54 +08:00
|
|
|
// Query APIs for convenience purpose.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-03-22 23:26:15 +08:00
|
|
|
GetAll(sql string, args ...interface{}) (Result, error)
|
|
|
|
GetOne(sql string, args ...interface{}) (Record, error)
|
|
|
|
GetValue(sql string, args ...interface{}) (Value, error)
|
|
|
|
GetArray(sql string, args ...interface{}) ([]Value, error)
|
|
|
|
GetCount(sql string, args ...interface{}) (int, error)
|
|
|
|
GetStruct(objPointer interface{}, sql string, args ...interface{}) error
|
|
|
|
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error
|
|
|
|
GetScan(objPointer interface{}, sql string, args ...interface{}) error
|
2018-12-15 15:50:39 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2020-03-08 00:17:42 +08:00
|
|
|
// Master/Slave specification support.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2019-06-19 09:06:52 +08:00
|
|
|
Master() (*sql.DB, error)
|
|
|
|
Slave() (*sql.DB, error)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2020-03-08 00:17:42 +08:00
|
|
|
// Ping-Pong.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2018-08-08 09:09:28 +08:00
|
|
|
PingMaster() error
|
|
|
|
PingSlave() error
|
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2019-12-23 23:14:54 +08:00
|
|
|
// Transaction.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2018-12-15 15:50:39 +08:00
|
|
|
Begin() (*TX, error)
|
2020-04-26 17:47:19 +08:00
|
|
|
Transaction(f func(tx *TX) error) (err error)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2019-12-23 23:14:54 +08:00
|
|
|
// Configuration methods.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
GetCache() *gcache.Cache
|
2019-06-19 09:06:52 +08:00
|
|
|
SetDebug(debug bool)
|
2020-03-08 00:17:42 +08:00
|
|
|
GetDebug() bool
|
2019-06-19 09:06:52 +08:00
|
|
|
SetSchema(schema string)
|
2020-03-08 00:17:42 +08:00
|
|
|
GetSchema() string
|
|
|
|
GetPrefix() string
|
2020-04-08 21:26:14 +08:00
|
|
|
GetGroup() string
|
2020-04-02 20:52:37 +08:00
|
|
|
SetDryRun(dryrun bool)
|
|
|
|
GetDryRun() bool
|
2019-08-26 19:39:04 +08:00
|
|
|
SetLogger(logger *glog.Logger)
|
2019-12-04 16:04:52 +08:00
|
|
|
GetLogger() *glog.Logger
|
2020-10-20 21:01:39 +08:00
|
|
|
GetConfig() *ConfigNode
|
2019-06-29 18:17:33 +08:00
|
|
|
SetMaxIdleConnCount(n int)
|
|
|
|
SetMaxOpenConnCount(n int)
|
2019-09-24 23:41:18 +08:00
|
|
|
SetMaxConnLifetime(d time.Duration)
|
2020-03-08 00:17:42 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2020-03-08 00:17:42 +08:00
|
|
|
// Utility methods.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-11-29 23:47:57 +08:00
|
|
|
GetCtx() context.Context
|
2020-03-08 00:17:42 +08:00
|
|
|
GetChars() (charLeft string, charRight string)
|
|
|
|
GetMaster(schema ...string) (*sql.DB, error)
|
|
|
|
GetSlave(schema ...string) (*sql.DB, error)
|
|
|
|
QuoteWord(s string) string
|
|
|
|
QuoteString(s string) string
|
2020-03-09 21:53:58 +08:00
|
|
|
QuotePrefixTableName(table string) string
|
2020-01-07 22:14:32 +08:00
|
|
|
Tables(schema ...string) (tables []string, err error)
|
|
|
|
TableFields(table string, schema ...string) (map[string]*TableField, error)
|
2020-07-17 11:28:47 +08:00
|
|
|
HasTable(name string) (bool, error)
|
2021-01-25 21:17:32 +08:00
|
|
|
FilteredLinkInfo() string
|
2018-12-14 18:35:51 +08:00
|
|
|
|
2020-03-09 22:00:01 +08:00
|
|
|
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
|
2020-03-09 21:53:58 +08:00
|
|
|
// it's committed to underlying driver. The parameter <link> specifies the current
|
2020-03-22 23:26:15 +08:00
|
|
|
// database connection operation object. You can modify the sql string <sql> and its
|
2020-03-09 21:53:58 +08:00
|
|
|
// arguments <args> as you wish before they're committed to driver.
|
2020-03-22 23:26:15 +08:00
|
|
|
HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{})
|
2020-03-09 21:53:58 +08:00
|
|
|
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
2020-11-29 23:47:57 +08:00
|
|
|
// Internal methods, for internal usage purpose, you do not need consider it.
|
2020-07-08 20:48:29 +08:00
|
|
|
// ===========================================================================
|
|
|
|
|
2020-10-17 18:16:13 +08:00
|
|
|
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
|
2020-11-29 23:47:57 +08:00
|
|
|
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{}
|
|
|
|
convertRowsToResult(rows *sql.Rows) (Result, error)
|
2018-12-15 15:50:39 +08:00
|
|
|
}
|
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
// Core is the base struct for database management.
|
|
|
|
type Core struct {
|
2020-11-29 23:47:57 +08:00
|
|
|
DB DB // DB interface object.
|
|
|
|
group string // Configuration group name.
|
|
|
|
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
|
|
|
|
cache *gcache.Cache // Cache manager, SQL result cache only.
|
|
|
|
schema *gtype.String // Custom schema for this object.
|
|
|
|
logger *glog.Logger // Logger.
|
|
|
|
config *ConfigNode // Current config node.
|
|
|
|
ctx context.Context // Context for chaining operation only.
|
2018-07-05 19:36:35 +08:00
|
|
|
}
|
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
// Driver is the interface for integrating sql drivers into package gdb.
|
|
|
|
type Driver interface {
|
2020-03-08 11:03:18 +08:00
|
|
|
// New creates and returns a database object for specified database server.
|
2020-03-08 00:17:42 +08:00
|
|
|
New(core *Core, node *ConfigNode) (DB, error)
|
|
|
|
}
|
|
|
|
|
2020-01-07 22:14:32 +08:00
|
|
|
// Sql is the sql recording struct.
|
2018-07-05 19:36:35 +08:00
|
|
|
type Sql struct {
|
2020-07-30 23:00:20 +08:00
|
|
|
Sql string // SQL string(may contain reserved char '?').
|
2021-01-25 21:17:32 +08:00
|
|
|
Type string // SQL operation type.
|
2020-07-30 23:00:20 +08:00
|
|
|
Args []interface{} // Arguments for this sql.
|
|
|
|
Format string // Formatted sql which contains arguments in the sql.
|
|
|
|
Error error // Execution result.
|
|
|
|
Start int64 // Start execution timestamp in milliseconds.
|
|
|
|
End int64 // End execution timestamp in milliseconds.
|
|
|
|
Group string // Group is the group name of the configuration that the sql is executed from.
|
2017-11-23 10:21:28 +08:00
|
|
|
}
|
|
|
|
|
2020-02-08 23:46:10 +08:00
|
|
|
// TableField is the struct for table field.
|
2019-09-02 15:48:25 +08:00
|
|
|
type TableField struct {
|
2020-02-08 23:46:10 +08:00
|
|
|
Index int // For ordering purpose as map is unordered.
|
|
|
|
Name string // Field name.
|
|
|
|
Type string // Field type.
|
|
|
|
Null bool // Field can be null or not.
|
|
|
|
Key string // The index information(empty if it's not a index).
|
|
|
|
Default interface{} // Default value for the field.
|
|
|
|
Extra string // Extra information.
|
|
|
|
Comment string // Comment.
|
2019-09-02 15:48:25 +08:00
|
|
|
}
|
|
|
|
|
2020-03-09 21:53:58 +08:00
|
|
|
// Link is a common database function wrapper interface.
|
|
|
|
type Link interface {
|
2020-12-29 14:01:49 +08:00
|
|
|
Query(sql string, args ...interface{}) (*sql.Rows, error)
|
|
|
|
Exec(sql string, args ...interface{}) (sql.Result, error)
|
|
|
|
Prepare(sql string) (*sql.Stmt, error)
|
2021-01-25 21:17:32 +08:00
|
|
|
QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error)
|
|
|
|
ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error)
|
|
|
|
PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error)
|
2020-03-08 00:17:42 +08:00
|
|
|
}
|
|
|
|
|
2020-11-28 13:52:17 +08:00
|
|
|
// Counter is the type for update count.
|
|
|
|
type Counter struct {
|
|
|
|
Field string
|
|
|
|
Value float64
|
|
|
|
}
|
|
|
|
|
2020-04-08 21:26:14 +08:00
|
|
|
type (
|
2020-12-09 01:29:23 +08:00
|
|
|
Raw string // Raw is a raw sql that will not be treated as argument but as a direct sql part.
|
|
|
|
Value = *gvar.Var // Value is the field value type.
|
|
|
|
Record map[string]Value // Record is the row record of the table.
|
|
|
|
Result []Record // Result is the row record array.
|
|
|
|
Map = map[string]interface{} // Map is alias of map[string]interface{}, which is the most common usage map type.
|
|
|
|
List = []Map // List is type of map array.
|
2020-04-08 21:26:14 +08:00
|
|
|
)
|
2017-11-23 10:21:28 +08:00
|
|
|
|
2018-12-28 21:56:27 +08:00
|
|
|
const (
|
2020-11-10 21:19:15 +08:00
|
|
|
insertOptionDefault = 0
|
|
|
|
insertOptionReplace = 1
|
|
|
|
insertOptionSave = 2
|
|
|
|
insertOptionIgnore = 3
|
2020-12-09 01:29:23 +08:00
|
|
|
defaultBatchNumber = 10 // Per count for batch insert/replace/save.
|
|
|
|
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
|
|
|
defaultMaxOpenConnCount = 100 // Max open connection count in pool.
|
|
|
|
defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds.
|
2021-01-26 11:09:50 +08:00
|
|
|
ctxTimeoutTypeExec = iota
|
|
|
|
ctxTimeoutTypeQuery
|
|
|
|
ctxTimeoutTypePrepare
|
2019-04-02 14:37:46 +08:00
|
|
|
)
|
2019-02-27 09:38:10 +08:00
|
|
|
|
2019-04-02 14:37:46 +08:00
|
|
|
var (
|
2020-03-22 23:26:15 +08:00
|
|
|
// ErrNoRows is alias of sql.ErrNoRows.
|
|
|
|
ErrNoRows = sql.ErrNoRows
|
2020-06-28 23:03:41 +08:00
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
// instances is the management map for instances.
|
2019-07-23 23:20:27 +08:00
|
|
|
instances = gmap.NewStrAnyMap(true)
|
2020-06-28 23:03:41 +08:00
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
// driverMap manages all custom registered driver.
|
|
|
|
driverMap = map[string]Driver{
|
|
|
|
"mysql": &DriverMysql{},
|
|
|
|
"mssql": &DriverMssql{},
|
2020-03-09 21:56:53 +08:00
|
|
|
"pgsql": &DriverPgsql{},
|
2020-03-08 00:17:42 +08:00
|
|
|
"oracle": &DriverOracle{},
|
|
|
|
"sqlite": &DriverSqlite{},
|
|
|
|
}
|
2020-06-28 23:03:41 +08:00
|
|
|
|
|
|
|
// lastOperatorRegPattern is the regular expression pattern for a string
|
|
|
|
// which has operator at its tail.
|
|
|
|
lastOperatorRegPattern = `[<>=]+\s*$`
|
|
|
|
|
|
|
|
// regularFieldNameRegPattern is the regular expression pattern for a string
|
|
|
|
// which is a regular field name of table.
|
2020-12-04 14:22:50 +08:00
|
|
|
regularFieldNameRegPattern = `^[\w\.\-\_]+$`
|
2020-07-30 23:08:27 +08:00
|
|
|
|
2021-01-20 13:09:59 +08:00
|
|
|
// regularFieldNameWithoutDotRegPattern is similar to regularFieldNameRegPattern but not allows '.'.
|
|
|
|
// Note that, although some databases allow char '.' in the field name, but it here does not allow '.'
|
|
|
|
// in the field name as it conflicts with "db.table.field" pattern in SOME situations.
|
|
|
|
regularFieldNameWithoutDotRegPattern = `^[\w\-\_]+$`
|
|
|
|
|
2020-11-07 20:00:35 +08:00
|
|
|
// internalCache is the memory cache for internal usage.
|
|
|
|
internalCache = gcache.New()
|
|
|
|
|
2020-07-30 23:08:27 +08:00
|
|
|
// allDryRun sets dry-run feature for all database connections.
|
|
|
|
// It is commonly used for command options for convenience.
|
|
|
|
allDryRun = false
|
2018-12-28 21:56:27 +08:00
|
|
|
)
|
|
|
|
|
2020-07-30 23:08:27 +08:00
|
|
|
func init() {
|
|
|
|
// allDryRun is initialized from environment or command options.
|
2020-12-04 23:44:54 +08:00
|
|
|
allDryRun = gcmd.GetWithEnv("gf.gdb.dryrun", false).Bool()
|
2020-07-30 23:08:27 +08:00
|
|
|
}
|
|
|
|
|
2020-03-08 00:17:42 +08:00
|
|
|
// Register registers custom database driver to gdb.
|
|
|
|
func Register(name string, driver Driver) error {
|
|
|
|
driverMap[name] = driver
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-10 21:14:15 +08:00
|
|
|
// New creates and returns an ORM object with global configurations.
|
2019-06-11 20:57:43 +08:00
|
|
|
// The parameter <name> specifies the configuration group name,
|
2020-12-14 19:34:02 +08:00
|
|
|
// which is DefaultGroupName in default.
|
2020-08-31 15:39:27 +08:00
|
|
|
func New(group ...string) (db DB, err error) {
|
|
|
|
groupName := configs.group
|
|
|
|
if len(group) > 0 && group[0] != "" {
|
|
|
|
groupName = group[0]
|
2018-08-08 09:09:28 +08:00
|
|
|
}
|
2019-04-02 14:37:46 +08:00
|
|
|
configs.RLock()
|
|
|
|
defer configs.RUnlock()
|
2018-08-08 09:09:28 +08:00
|
|
|
|
2019-04-02 14:37:46 +08:00
|
|
|
if len(configs.config) < 1 {
|
2020-12-09 16:04:29 +08:00
|
|
|
return nil, gerror.New("empty database configuration")
|
2018-08-08 09:09:28 +08:00
|
|
|
}
|
2020-08-31 15:39:27 +08:00
|
|
|
if _, ok := configs.config[groupName]; ok {
|
|
|
|
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
|
2020-03-08 00:17:42 +08:00
|
|
|
c := &Core{
|
2020-11-27 13:24:05 +08:00
|
|
|
group: groupName,
|
|
|
|
debug: gtype.NewBool(),
|
|
|
|
cache: gcache.New(),
|
|
|
|
schema: gtype.NewString(),
|
|
|
|
logger: glog.New(),
|
|
|
|
config: node,
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2020-03-08 00:17:42 +08:00
|
|
|
if v, ok := driverMap[node.Type]; ok {
|
|
|
|
c.DB, err = v.New(c, node)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return c.DB, nil
|
|
|
|
} else {
|
2020-12-09 16:04:29 +08:00
|
|
|
return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-08-08 09:09:28 +08:00
|
|
|
} else {
|
2020-12-09 16:04:29 +08:00
|
|
|
return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
|
2018-08-08 09:09:28 +08:00
|
|
|
}
|
2017-11-23 10:21:28 +08:00
|
|
|
}
|
|
|
|
|
2019-04-03 23:39:31 +08:00
|
|
|
// Instance returns an instance for DB operations.
|
2019-06-11 20:57:43 +08:00
|
|
|
// The parameter <name> specifies the configuration group name,
|
2020-12-14 19:34:02 +08:00
|
|
|
// which is DefaultGroupName in default.
|
2019-04-03 23:39:31 +08:00
|
|
|
func Instance(name ...string) (db DB, err error) {
|
2020-02-10 20:37:53 +08:00
|
|
|
group := configs.group
|
2019-10-17 20:31:03 +08:00
|
|
|
if len(name) > 0 && name[0] != "" {
|
2019-06-19 09:06:52 +08:00
|
|
|
group = name[0]
|
|
|
|
}
|
|
|
|
v := instances.GetOrSetFuncLock(group, func() interface{} {
|
|
|
|
db, err = New(group)
|
|
|
|
return db
|
|
|
|
})
|
|
|
|
if v != nil {
|
|
|
|
return v.(DB), nil
|
|
|
|
}
|
|
|
|
return
|
2019-04-02 14:37:46 +08:00
|
|
|
}
|
|
|
|
|
2020-02-26 11:54:46 +08:00
|
|
|
// getConfigNodeByGroup calculates and returns a configuration node of given group. It
|
|
|
|
// calculates the value internally using weight algorithm for load balance.
|
2020-02-08 23:46:10 +08:00
|
|
|
//
|
|
|
|
// The parameter <master> specifies whether retrieving a master node, or else a slave node
|
|
|
|
// if master-slave configured.
|
2018-10-13 20:29:27 +08:00
|
|
|
func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
|
2019-06-19 09:06:52 +08:00
|
|
|
if list, ok := configs.config[group]; ok {
|
2020-02-08 23:46:10 +08:00
|
|
|
// Separates master and slave configuration nodes array.
|
2019-06-19 09:06:52 +08:00
|
|
|
masterList := make(ConfigGroup, 0)
|
|
|
|
slaveList := make(ConfigGroup, 0)
|
|
|
|
for i := 0; i < len(list); i++ {
|
|
|
|
if list[i].Role == "slave" {
|
|
|
|
slaveList = append(slaveList, list[i])
|
|
|
|
} else {
|
|
|
|
masterList = append(masterList, list[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(masterList) < 1 {
|
2020-12-09 16:04:29 +08:00
|
|
|
return nil, gerror.New("at least one master node configuration's need to make sense")
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
|
|
|
if len(slaveList) < 1 {
|
|
|
|
slaveList = masterList
|
|
|
|
}
|
|
|
|
if master {
|
2019-07-06 16:14:45 +08:00
|
|
|
return getConfigNodeByWeight(masterList), nil
|
2019-06-19 09:06:52 +08:00
|
|
|
} else {
|
2019-07-06 16:14:45 +08:00
|
|
|
return getConfigNodeByWeight(slaveList), nil
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
|
|
|
} else {
|
2020-12-09 16:04:29 +08:00
|
|
|
return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2018-10-13 20:29:27 +08:00
|
|
|
}
|
|
|
|
|
2020-02-08 23:46:10 +08:00
|
|
|
// getConfigNodeByWeight calculates the configuration weights and randomly returns a node.
|
|
|
|
//
|
|
|
|
// Calculation algorithm brief:
|
|
|
|
// 1. If we have 2 nodes, and their weights are both 1, then the weight range is [0, 199];
|
|
|
|
// 2. Node1 weight range is [0, 99], and node2 weight range is [100, 199], ratio is 1:1;
|
|
|
|
// 3. If the random number is 99, it then chooses and returns node1;
|
2019-07-06 16:14:45 +08:00
|
|
|
func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
|
2018-08-08 09:09:28 +08:00
|
|
|
if len(cg) < 2 {
|
|
|
|
return &cg[0]
|
|
|
|
}
|
|
|
|
var total int
|
|
|
|
for i := 0; i < len(cg); i++ {
|
2019-07-06 16:14:45 +08:00
|
|
|
total += cg[i].Weight * 100
|
2018-08-08 09:09:28 +08:00
|
|
|
}
|
2020-02-08 23:46:10 +08:00
|
|
|
// If total is 0 means all of the nodes have no weight attribute configured.
|
|
|
|
// It then defaults each node's weight attribute to 1.
|
2018-12-16 23:11:15 +08:00
|
|
|
if total == 0 {
|
2019-06-19 09:06:52 +08:00
|
|
|
for i := 0; i < len(cg); i++ {
|
2019-07-06 16:14:45 +08:00
|
|
|
cg[i].Weight = 1
|
|
|
|
total += cg[i].Weight * 100
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
|
|
|
}
|
2020-02-08 23:46:10 +08:00
|
|
|
// Exclude the right border value.
|
|
|
|
r := grand.N(0, total-1)
|
2018-08-08 09:09:28 +08:00
|
|
|
min := 0
|
|
|
|
max := 0
|
|
|
|
for i := 0; i < len(cg); i++ {
|
2019-07-06 16:14:45 +08:00
|
|
|
max = min + cg[i].Weight*100
|
2018-08-08 09:09:28 +08:00
|
|
|
//fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max)
|
|
|
|
if r >= min && r < max {
|
|
|
|
return &cg[i]
|
|
|
|
} else {
|
|
|
|
min = max
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2017-11-23 10:21:28 +08:00
|
|
|
}
|
|
|
|
|
2020-01-07 22:14:32 +08:00
|
|
|
// getSqlDb retrieves and returns a underlying database connection object.
|
|
|
|
// The parameter <master> specifies whether retrieves master node connection if
|
|
|
|
// master-slave nodes are configured.
|
2020-03-08 00:17:42 +08:00
|
|
|
func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
|
2020-01-07 22:14:32 +08:00
|
|
|
// Load balance.
|
2020-03-08 00:17:42 +08:00
|
|
|
node, err := getConfigNodeByGroup(c.group, master)
|
2019-06-19 09:06:52 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-07 22:14:32 +08:00
|
|
|
// Default value checks.
|
2019-06-19 09:06:52 +08:00
|
|
|
if node.Charset == "" {
|
|
|
|
node.Charset = "utf8"
|
|
|
|
}
|
2020-01-07 22:14:32 +08:00
|
|
|
// Changes the schema.
|
2020-03-08 00:17:42 +08:00
|
|
|
nodeSchema := c.schema.Val()
|
2020-01-07 22:14:32 +08:00
|
|
|
if len(schema) > 0 && schema[0] != "" {
|
|
|
|
nodeSchema = schema[0]
|
|
|
|
}
|
|
|
|
if nodeSchema != "" {
|
|
|
|
// Value copy.
|
|
|
|
n := *node
|
|
|
|
n.Name = nodeSchema
|
|
|
|
node = &n
|
|
|
|
}
|
2020-03-13 17:21:30 +08:00
|
|
|
// Cache the underlying connection pool object by node.
|
2020-11-07 20:00:35 +08:00
|
|
|
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
2020-03-08 00:17:42 +08:00
|
|
|
sqlDb, err = c.DB.Open(node)
|
2019-06-19 09:06:52 +08:00
|
|
|
if err != nil {
|
2020-03-19 13:38:42 +08:00
|
|
|
intlog.Printf("DB open failed: %v, %+v", err, node)
|
2020-10-09 20:59:49 +08:00
|
|
|
return nil, err
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2020-11-27 13:24:05 +08:00
|
|
|
if c.config.MaxIdleConnCount > 0 {
|
|
|
|
sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount)
|
2020-12-09 01:29:23 +08:00
|
|
|
} else {
|
|
|
|
sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount)
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2020-11-27 13:24:05 +08:00
|
|
|
if c.config.MaxOpenConnCount > 0 {
|
|
|
|
sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount)
|
2020-12-09 01:29:23 +08:00
|
|
|
} else {
|
|
|
|
sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount)
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2020-11-27 13:24:05 +08:00
|
|
|
if c.config.MaxConnLifetime > 0 {
|
|
|
|
// Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc.
|
|
|
|
// Or else it is configured just using number, which means value in seconds.
|
|
|
|
if c.config.MaxConnLifetime > time.Second {
|
|
|
|
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime)
|
|
|
|
} else {
|
|
|
|
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second)
|
|
|
|
}
|
2020-12-09 01:29:23 +08:00
|
|
|
} else {
|
|
|
|
sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime)
|
2019-06-19 09:06:52 +08:00
|
|
|
}
|
2020-10-09 20:59:49 +08:00
|
|
|
return sqlDb, nil
|
2019-06-19 09:06:52 +08:00
|
|
|
}, 0)
|
|
|
|
if v != nil && sqlDb == nil {
|
|
|
|
sqlDb = v.(*sql.DB)
|
|
|
|
}
|
2019-07-15 19:54:13 +08:00
|
|
|
if node.Debug {
|
2020-03-08 00:17:42 +08:00
|
|
|
c.DB.SetDebug(node.Debug)
|
2019-07-15 17:40:21 +08:00
|
|
|
}
|
2020-04-02 20:52:37 +08:00
|
|
|
if node.Debug {
|
|
|
|
c.DB.SetDryRun(node.DryRun)
|
|
|
|
}
|
2019-06-19 09:06:52 +08:00
|
|
|
return
|
2018-10-13 20:29:27 +08:00
|
|
|
}
|