diff --git a/.github/workflows/gf.yml b/.github/workflows/gf.yml index bd4153a20..b7ad3678e 100644 --- a/.github/workflows/gf.yml +++ b/.github/workflows/gf.yml @@ -119,7 +119,6 @@ jobs: cd cmd/gf go mod tidy go build ./... - go test ./... GOARCH=386 go test ./... || exit 1 GOARCH=amd64 go test ./... -race -coverprofile=coverage.txt -covermode=atomic || exit 1 @@ -148,12 +147,10 @@ jobs: cd - done - - name: Run i386 Arch Test - run: | - GOARCH=386 go test -v ./... || exit 1 - - - name: Run amd64 Arch Test + - name: Main Build & Test run: | + go build ./... + GOARCH=386 go test -v ./... || exit 1 GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic || exit 1 - name: Report Coverage diff --git a/contrib/drivers/mysql/mysql.go b/contrib/drivers/mysql/mysql.go index 5cc5c1149..af622a51b 100644 --- a/contrib/drivers/mysql/mysql.go +++ b/contrib/drivers/mysql/mysql.go @@ -39,7 +39,7 @@ func init() { // New create and returns a driver that implements gdb.Driver, which supports operations for MySQL. func New() gdb.Driver { - return &gdb.DriverMysql{} + return &DriverMysql{} } // New creates and returns a database object for mysql. diff --git a/contrib/drivers/mysql/mysql_core_test.go b/contrib/drivers/mysql/mysql_core_test.go index a14cf1966..9103fde7c 100644 --- a/contrib/drivers/mysql/mysql_core_test.go +++ b/contrib/drivers/mysql/mysql_core_test.go @@ -29,7 +29,7 @@ func Test_New(t *testing.T) { Port: "3306", User: TestDbUser, Pass: TestDbPass, - Type: gdb.DriverNameMysql, + Type: "mysql", } newDb, err := gdb.New(node) t.AssertNil(err) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index a128d589c..334a7bcd3 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -311,18 +311,12 @@ const ( SqlTypeStmtQueryRowContext = "DB.Statement.QueryRowContext" ) -const ( - DriverNameMysql = `mysql` -) - var ( // instances is the management map for instances. instances = gmap.NewStrAnyMap(true) // driverMap manages all custom registered driver. - driverMap = map[string]Driver{ - DriverNameMysql: &DriverMysql{}, - } + driverMap = map[string]Driver{} // lastOperatorRegPattern is the regular expression pattern for a string // which has operator at its tail. @@ -559,11 +553,12 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error intlog.Printf(ctx, `open new connection success, master:%#v, config:%#v, node:%#v`, master, c.config, node) } }() - if sqlDb, err = c.db.Open(node); err != nil { return nil } - + if sqlDb == nil { + return nil + } if c.config.MaxIdleConnCount > 0 { sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount) } else { diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 03ad38f06..5ec62343c 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -635,6 +635,12 @@ func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (c *Core) FilteredLink() string { + return c.config.Link +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. // It just returns the pointer address. // diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index ca60f6906..aeef81aa0 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -218,8 +218,8 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s // mappingAndFilterData automatically mappings the map key to table field and removes // all key-value pairs that are not the field of given table. -func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) { - fieldsMap, err := c.TableFields(c.guessPrimaryTableName(table), schema) +func (c *Core) mappingAndFilterData(ctx context.Context, schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) { + fieldsMap, err := c.db.TableFields(ctx, c.guessPrimaryTableName(table), schema) if err != nil { return nil, err } diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go index fab680639..f8324d5dd 100644 --- a/database/gdb/gdb_core_utility.go +++ b/database/gdb/gdb_core_utility.go @@ -135,33 +135,30 @@ func (c *Core) GetChars() (charLeft string, charRight string) { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -// -// It does nothing in default. -func (c *Core) Tables(schema ...string) (tables []string, err error) { +func (c *Core) Tables(ctx context.Context, schema ...string) (tables []string, err error) { return } -// TableFields retrieves and returns the fields' information of specified table of current schema. +// TableFields retrieves and returns the fields' information of specified table of current +// schema. +// +// The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection +// as its link to proceed necessary sql query. // // Note that it returns a map containing the field name and its corresponding fields. -// As a map is unsorted, the TableField struct has an "Index" field marks its sequence in the fields. +// As a map is unsorted, the TableField struct has an "Index" field marks its sequence in +// the fields. // -// It's using cache feature to enhance the performance, which is never expired util the process restarts. -// -// It does nothing in default. -func (c *Core) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { - var ctx = c.db.GetCtx() - // It does nothing if given table is empty, especially in sub-query. - if table == "" { - return map[string]*TableField{}, nil - } - return c.db.TableFields(ctx, table, schema...) +// It's using cache feature to enhance the performance, which is never expired util the +// process restarts. +func (c *Core) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { + return } // HasField determine whether the field exists in the table. -func (c *Core) HasField(table, field string, schema ...string) (bool, error) { +func (c *Core) HasField(ctx context.Context, table, field string, schema ...string) (bool, error) { table = c.guessPrimaryTableName(table) - tableFields, err := c.TableFields(table, schema...) + tableFields, err := c.db.TableFields(ctx, table, schema...) if err != nil { return false, err } diff --git a/database/gdb/gdb_default_driver.go b/database/gdb/gdb_default_driver.go new file mode 100644 index 000000000..82168e597 --- /dev/null +++ b/database/gdb/gdb_default_driver.go @@ -0,0 +1,46 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// 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, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "database/sql" +) + +// DriverTest is the driver for mysql database. +type DriverTest struct { + *Core +} + +func init() { + if err := Register("test", &DriverTest{}); err != nil { + panic(err) + } +} + +// New creates and returns a database object for mysql. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *DriverTest) New(core *Core, node *ConfigNode) (DB, error) { + return &DriverTest{ + Core: core, + }, nil +} + +// Open creates and returns an underlying sql.DB object for mysql. +// Note that it converts time.Time argument to local timezone in default. +func (d *DriverTest) Open(config *ConfigNode) (db *sql.DB, err error) { + return +} + +// PingMaster pings the master node to check authentication or keeps the connection alive. +func (d *DriverTest) PingMaster() error { + return nil +} + +// PingSlave pings the slave node to check authentication or keeps the connection alive. +func (d *DriverTest) PingSlave() error { + return nil +} diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go deleted file mode 100644 index 1186b925d..000000000 --- a/database/gdb/gdb_driver_mysql.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. -// -// 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, -// You can obtain one at https://github.com/gogf/gf. - -package gdb - -import ( - "context" - "database/sql" - "fmt" - "net/url" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/intlog" - "github.com/gogf/gf/v2/text/gregex" - "github.com/gogf/gf/v2/text/gstr" -) - -// DriverMysql is the driver for mysql database. -type DriverMysql struct { - *Core -} - -// New creates and returns a database object for mysql. -// It implements the interface of gdb.Driver for extra database driver installation. -func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) { - return &DriverMysql{ - Core: core, - }, nil -} - -// Open creates and returns an underlying sql.DB object for mysql. -// Note that it converts time.Time argument to local timezone in default. -func (d *DriverMysql) Open(config *ConfigNode) (db *sql.DB, err error) { - var ( - ctx = d.GetCtx() - source string - underlyingDriverName = "mysql" - ) - // [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] - if config.Link != "" { - source = config.Link - // Custom changing the schema in runtime. - if config.Name != "" { - source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source) - } - } else { - source = fmt.Sprintf( - "%s:%s@tcp(%s:%s)/%s?charset=%s", - config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset, - ) - if config.Timezone != "" { - source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone)) - } - } - intlog.Printf(ctx, "Open: %s", source) - if db, err = sql.Open(underlyingDriverName, source); err != nil { - err = gerror.WrapCodef( - gcode.CodeDbOperationError, err, - `sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source, - ) - return nil, err - } - return -} - -// FilteredLink retrieves and returns filtered `linkInfo` that can be using for -// logging or tracing purpose. -func (d *DriverMysql) FilteredLink() string { - linkInfo := d.GetConfig().Link - if linkInfo == "" { - return "" - } - s, _ := gregex.ReplaceString( - `(.+?):(.+)@tcp(.+)`, - `$1:xxx@tcp$3`, - linkInfo, - ) - return s -} - -// GetChars returns the security char for this type of database. -func (d *DriverMysql) GetChars() (charLeft string, charRight string) { - return "`", "`" -} - -// DoFilter handles the sql before posts it to database. -func (d *DriverMysql) DoFilter(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { - return d.Core.DoFilter(ctx, link, sql, args) -} - -// Tables retrieves and returns the tables of current schema. -// It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { - var result Result - link, err := d.SlaveLink(schema...) - if err != nil { - return nil, err - } - result, err = d.DoSelect(ctx, link, `SHOW TABLES`) - if err != nil { - return - } - for _, m := range result { - for _, v := range m { - tables = append(tables, v.String()) - } - } - return -} - -// TableFields retrieves and returns the fields' information of specified table of current -// schema. -// -// The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection -// as its link to proceed necessary sql query. -// -// Note that it returns a map containing the field name and its corresponding fields. -// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in -// the fields. -// -// It's using cache feature to enhance the performance, which is never expired util the -// process restarts. -func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { - charL, charR := d.GetChars() - table = gstr.Trim(table, charL+charR) - if gstr.Contains(table, " ") { - return nil, gerror.NewCode( - gcode.CodeInvalidParameter, - "function TableFields supports only single table operations", - ) - } - useSchema := d.schema - if len(schema) > 0 && schema[0] != "" { - useSchema = schema[0] - } - v := tableFieldsMap.GetOrSetFuncLock( - fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, useSchema, d.GetGroup()), - func() interface{} { - var ( - result Result - link Link - ) - if link, err = d.SlaveLink(useSchema); err != nil { - return nil - } - result, err = d.DoSelect( - ctx, link, - fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)), - ) - if err != nil { - return nil - } - fields = make(map[string]*TableField) - for i, m := range result { - fields[m["Field"].String()] = &TableField{ - Index: i, - Name: m["Field"].String(), - Type: m["Type"].String(), - Null: m["Null"].Bool(), - Key: m["Key"].String(), - Default: m["Default"].Val(), - Extra: m["Extra"].String(), - Comment: m["Comment"].String(), - } - } - return fields - }, - ) - if v != nil { - fields = v.(map[string]*TableField) - } - return -} diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index d652ad60c..0f22ed18d 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -446,7 +446,7 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n } // Mapping and filtering fields if `Table` is given. if in.Table != "" { - data, _ = db.GetCore().mappingAndFilterData(in.Schema, in.Table, data, true) + data, _ = db.GetCore().mappingAndFilterData(ctx, in.Schema, in.Table, data, true) } // Put the struct attributes in sequence in Where statement. for i := 0; i < reflectType.NumField(); i++ { @@ -506,7 +506,7 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n // If the first part is column name, it automatically adds prefix to the column. if in.Prefix != "" { array := gstr.Split(whereStr, " ") - if ok, _ := db.GetCore().HasField(in.Table, array[0]); ok { + if ok, _ := db.GetCore().HasField(ctx, in.Table, array[0]); ok { whereStr = in.Prefix + "." + whereStr } } diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index cc3f13011..935d09efb 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -249,5 +249,5 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string { // HasField determine whether the field exists in the table. func (m *Model) HasField(field string) (bool, error) { - return m.db.GetCore().HasField(m.tablesInit, field) + return m.db.GetCore().HasField(m.GetCtx(), m.tablesInit, field) } diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 2559217e8..08ecab7c0 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -38,7 +38,7 @@ func (m *Model) TableFields(tableStr string, schema ...string) (fields map[strin if len(schema) > 0 && schema[0] != "" { useSchema = schema[0] } - return m.db.GetCore().TableFields(table, useSchema) + return m.db.TableFields(m.GetCtx(), table, useSchema) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns @@ -120,7 +120,7 @@ func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, erro func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) { var err error data, err = m.db.GetCore().mappingAndFilterData( - m.schema, m.tablesInit, data, m.filter, + m.GetCtx(), m.schema, m.tablesInit, data, m.filter, ) if err != nil { return nil, err diff --git a/frame/gins/testdata/database/config.toml b/frame/gins/testdata/database/config.toml index 5ce6c3c7f..40f310a7d 100644 --- a/frame/gins/testdata/database/config.toml +++ b/frame/gins/testdata/database/config.toml @@ -9,7 +9,7 @@ test = "v=2" user = "root" pass = "12345678" name = "test" - type = "mysql" + type = "test" role = "master" weight = "1" charset = "utf8" @@ -19,7 +19,7 @@ test = "v=2" user = "root" pass = "12345678" name = "test" - type = "mysql" + type = "test" role = "master" weight = "1" charset = "utf8"