diff --git a/.gitignore b/.gitignore index 79efb0edc..fdb500de8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ cbuild **/.DS_Store .vscode/ .example/other/ +main +gf \ No newline at end of file diff --git a/container/gvar/gvar_is.go b/container/gvar/gvar_is.go index a7d2f14c7..b7ac9af48 100644 --- a/container/gvar/gvar_is.go +++ b/container/gvar/gvar_is.go @@ -7,92 +7,45 @@ package gvar import ( - "github.com/gogf/gf/internal/empty" - "reflect" + "github.com/gogf/gf/internal/utils" ) -// IsNil checks whether is nil. +// IsNil checks whether `v` is nil. func (v *Var) IsNil() bool { - return v.Val() == nil + return utils.IsNil(v.Val()) } -// IsEmpty checks whether is empty. +// IsEmpty checks whether `v` is empty. func (v *Var) IsEmpty() bool { - return empty.IsEmpty(v.Val()) + return utils.IsEmpty(v.Val()) } -// IsInt checks whether is type of int. +// IsInt checks whether `v` is type of int. func (v *Var) IsInt() bool { - switch v.Val().(type) { - case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64: - return true - } - return false + return utils.IsInt(v.Val()) } -// IsUint checks whether is type of uint. +// IsUint checks whether `v` is type of uint. func (v *Var) IsUint() bool { - switch v.Val().(type) { - case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64: - return true - } - return false + return utils.IsUint(v.Val()) } -// IsFloat checks whether is type of float. +// IsFloat checks whether `v` is type of float. func (v *Var) IsFloat() bool { - switch v.Val().(type) { - case float32, *float32, float64, *float64: - return true - } - return false + return utils.IsFloat(v.Val()) } -// IsSlice checks whether is type of slice. +// IsSlice checks whether `v` is type of slice. func (v *Var) IsSlice() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - } - switch reflectKind { - case reflect.Slice, reflect.Array: - return true - } - return false + return utils.IsSlice(v.Val()) } -// IsMap checks whether is type of map. +// IsMap checks whether `v` is type of map. func (v *Var) IsMap() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - } - switch reflectKind { - case reflect.Map: - return true - } - return false + return utils.IsMap(v.Val()) } -// IsStruct checks whether is type of struct. +// IsStruct checks whether `v` is type of struct. func (v *Var) IsStruct() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Struct: - return true - } - return false + return utils.IsStruct(v.Val()) } diff --git a/container/gvar/gvar_map.go b/container/gvar/gvar_map.go index 090dd372a..00a751a23 100644 --- a/container/gvar/gvar_map.go +++ b/container/gvar/gvar_map.go @@ -41,7 +41,7 @@ func (v *Var) MapDeep(tags ...string) map[string]interface{} { return gconv.MapDeep(v.Val(), tags...) } -// MapDeep converts and returns as map[string]string recursively. +// MapStrStrDeep converts and returns as map[string]string recursively. func (v *Var) MapStrStrDeep(tags ...string) map[string]string { return gconv.MapStrStrDeep(v.Val(), tags...) } diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index ab11e9b66..2b96dd8ec 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -353,7 +353,7 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e return c.Model(table).Data(data).Save() } -// DoInsert inserts or updates data for given table. +// DoInsert inserts or updates data forF given table. // This function is usually used for custom interface definition, you do not need call it manually. // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: @@ -510,29 +510,34 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter switch kind { case reflect.Map, reflect.Struct: var ( - fields []string - dataMap = ConvertDataForTableRecord(data) + fields []string + dataMap = ConvertDataForTableRecord(data) + counterHandler = func(column string, counter Counter) { + if counter.Value != 0 { + var ( + column = c.QuoteWord(column) + columnRef = c.QuoteWord(counter.Field) + columnVal = counter.Value + operator = "+" + ) + if columnVal < 0 { + operator = "-" + columnVal = -columnVal + } + fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator)) + params = append(params, columnVal) + } + } ) + for k, v := range dataMap { switch value := v.(type) { case *Counter: - if value.Value != 0 { - column := k - if value.Field != "" { - column = c.QuoteWord(value.Field) - } - fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) - params = append(params, value.Value) - } + counterHandler(k, *value) + case Counter: - if value.Value != 0 { - column := k - if value.Field != "" { - column = c.QuoteWord(value.Field) - } - fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) - params = append(params, value.Value) - } + counterHandler(k, value) + default: if s, ok := v.(Raw); ok { fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s)) @@ -543,6 +548,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter } } updates = strings.Join(fields, ",") + default: updates = gconv.String(data) } diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index 24156605c..f56c2f966 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -287,3 +287,17 @@ ORDER BY a.id,a.colorder`, } return } + +// DoInsert is not supported in mssql. +func (d *DriverMssql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.New(`Save operation is not supported by mssql driver`) + + case insertOptionReplace: + return nil, gerror.New(`Replace operation is not supported by mssql driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index 8c175f228..7ea5fcd54 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -263,7 +263,27 @@ func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[ return } +// DoInsert inserts or updates data for given table. +// This function is usually used for custom interface definition, you do not need call it manually. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// Eg: +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// The parameter `option` values are as follows: +// 0: insert: just insert, if there's unique/primary key in the data, it returns error; +// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; +// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; +// 3: ignore: if there's unique/primary key in the data, it ignores the inserting; func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.New(`Save operation is not supported by mssql driver`) + + case insertOptionReplace: + return nil, gerror.New(`Replace operation is not supported by mssql driver`) + } + var ( keys []string values []string diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index a1ada0252..3503313d9 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -142,9 +142,18 @@ func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...s result Result link, err = d.SlaveLink(useSchema) structureSql = fmt.Sprintf(` -SELECT a.attname AS field, t.typname AS type,b.description as comment FROM pg_class c, pg_attribute a -LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t -WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid +SELECT a.attname AS field, t.typname AS type,a.attnotnull as null, + (case when d.contype is not null then 'pri' else '' end) as key + ,ic.column_default as default_value,b.description as comment + ,coalesce(character_maximum_length, numeric_precision, -1) as length + ,numeric_scale as scale +FROM pg_attribute a + left join pg_class c on a.attrelid = c.oid + left join pg_constraint d on d.conrelid = c.oid and a.attnum = d.conkey[1] + left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid + left join pg_type t ON a.atttypid = t.oid + left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname +WHERE c.relname = '%s' and a.attnum > 0 ORDER BY a.attnum`, strings.ToLower(table), ) @@ -163,6 +172,9 @@ ORDER BY a.attnum`, Index: i, Name: m["field"].String(), Type: m["type"].String(), + Null: m["null"].Bool(), + Key: m["key"].String(), + Default: m["default_value"].Val(), Comment: m["comment"].String(), } } @@ -173,3 +185,17 @@ ORDER BY a.attnum`, } return } + +// DoInsert is not supported in pgsql. +func (d *DriverPgsql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.New(`Save operation is not supported by pgsql driver`) + + case insertOptionReplace: + return nil, gerror.New(`Replace operation is not supported by pgsql driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index 1ad0f68b3..132fe5c32 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -136,3 +136,17 @@ func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ... } return } + +// DoInsert is not supported in sqlite. +func (d *DriverSqlite) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.New(`Save operation is not supported by sqlite driver`) + + case insertOptionReplace: + return nil, gerror.New(`Replace operation is not supported by sqlite driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index e2a0b4c17..c5454c38f 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -70,6 +70,31 @@ var ( structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...) ) +// guessPrimaryTableName parses and returns the primary table name. +func (m *Model) guessPrimaryTableName(tableStr string) string { + if tableStr == "" { + return "" + } + var ( + guessedTableName = "" + array1 = gstr.SplitAndTrim(tableStr, ",") + array2 = gstr.SplitAndTrim(array1[0], " ") + array3 = gstr.SplitAndTrim(array2[0], ".") + ) + if len(array3) >= 2 { + guessedTableName = array3[1] + } + guessedTableName = array3[0] + charL, charR := m.db.GetChars() + if charL != "" || charR != "" { + guessedTableName = gstr.Trim(guessedTableName, charL+charR) + } + if !gregex.IsMatchString(regularFieldNameRegPattern, guessedTableName) { + return "" + } + return guessedTableName +} + // getTableNameFromOrmTag retrieves and returns the table name from struct object. func getTableNameFromOrmTag(object interface{}) string { var tableName string @@ -239,7 +264,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} { name = "" fieldTag = rtField.Tag for _, tag := range structTagPriority { - if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) { + if s := fieldTag.Get(tag); s != "" { name = s break } @@ -789,7 +814,9 @@ func formatError(err error, sql string, args ...interface{}) error { func FormatSqlWithArgs(sql string, args []interface{}) string { index := -1 newQuery, _ := gregex.ReplaceStringFunc( - `(\?|:v\d+|\$\d+|@p\d+)`, sql, func(s string) string { + `(\?|:v\d+|\$\d+|@p\d+)`, + sql, + func(s string) string { index++ if len(args) > index { if args[index] == nil { @@ -809,6 +836,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { switch kind { case reflect.String, reflect.Map, reflect.Slice, reflect.Array: return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'` + case reflect.Struct: if t, ok := args[index].(time.Time); ok { return `'` + t.Format(`2006-01-02 15:04:05`) + `'` diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 245398260..909103bf7 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -206,7 +206,7 @@ func (m *Model) As(as string) *Model { if m.tables != "" { model := m.getModel() split := " JOIN " - if gstr.Contains(model.tables, split) { + if gstr.ContainsI(model.tables, split) { // For join table. array := gstr.Split(model.tables, split) array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1]) diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index 46d7af223..1d3b8a626 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -90,13 +90,13 @@ func (m *Model) FieldsStr(prefix ...string) string { } // GetFieldsStr retrieves and returns all fields from the table, joined with char ','. -// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u."). +// The optional parameter `prefix` specifies the prefix for each field, eg: GetFieldsStr("u."). func (m *Model) GetFieldsStr(prefix ...string) string { prefixStr := "" if len(prefix) > 0 { prefixStr = prefix[0] } - tableFields, err := m.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -136,7 +136,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string { if len(prefix) > 0 { prefixStr = prefix[0] } - tableFields, err := m.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -164,7 +164,7 @@ 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) { - tableFields, err := m.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { return false, err } diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index cd39c4332..897eae92f 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -76,7 +76,7 @@ func (m *Model) getFieldsFiltered() string { panic("function FieldsEx supports only single table operations") } // Filter table fields with fieldEx. - tableFields, err := m.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -358,11 +358,11 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error { // parameter. // See the example or unit testing cases for clear understanding for this function. func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) { - all, err := m.All() + result, err := m.All() if err != nil { return err } - return all.ScanList(listPointer, attributeName, relation...) + return doScanList(m, result, listPointer, attributeName, relation...) } // Count does "SELECT COUNT(x) FROM ..." statement for the model. diff --git a/database/gdb/gdb_model_time.go b/database/gdb/gdb_model_time.go index 76c1f0667..0ca2a6877 100644 --- a/database/gdb/gdb_model_time.go +++ b/database/gdb/gdb_model_time.go @@ -40,7 +40,7 @@ func (m *Model) getSoftFieldNameCreated(table ...string) string { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.CreatedAt != "" { @@ -61,7 +61,7 @@ func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.UpdatedAt != "" { @@ -82,7 +82,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.UpdatedAt != "" { @@ -170,17 +170,3 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string { } return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field)) } - -// getPrimaryTableName parses and returns the primary table name. -func (m *Model) getPrimaryTableName() string { - if m.tables == "" { - return "" - } - array1 := gstr.SplitAndTrim(m.tables, ",") - array2 := gstr.SplitAndTrim(array1[0], " ") - array3 := gstr.SplitAndTrim(array2[0], ".") - if len(array3) >= 2 { - return array3[1] - } - return array3[0] -} diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 7b1c36b60..03f5e1ed3 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -21,15 +21,12 @@ import ( // schema. // // Also see DriverMysql.TableFields. -func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { - charL, charR := m.db.GetChars() - if charL != "" || charR != "" { - table = gstr.Trim(table, charL+charR) +func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) { + useSchema := m.schema + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] } - if !gregex.IsMatchString(regularFieldNameRegPattern, table) { - return nil, nil - } - return m.db.TableFields(m.GetCtx(), table, schema...) + return m.db.TableFields(m.GetCtx(), m.guessPrimaryTableName(tableStr), useSchema) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns @@ -47,7 +44,7 @@ func (m *Model) getModel() *Model { // ID -> id // NICK_Name -> nickname func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string { - fieldsMap, err := m.TableFields(m.tables) + fieldsMap, err := m.TableFields(m.tablesInit) if err != nil || len(fieldsMap) == 0 { return fields } @@ -201,7 +198,7 @@ func (m *Model) getLink(master bool) Link { // It parses m.tables to retrieve the primary table name, supporting m.tables like: // "user", "user u", "user as u, user_detail as ud". func (m *Model) getPrimaryKey() string { - table := gstr.SplitAndTrim(m.tables, " ")[0] + table := gstr.SplitAndTrim(m.tablesInit, " ")[0] tableFields, err := m.TableFields(table) if err != nil { return "" diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 1fbd891d3..12ab46f55 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -39,7 +39,10 @@ func (m *Model) With(objects ...interface{}) *Model { model := m.getModel() for _, object := range objects { if m.tables == "" { - m.tables = m.db.GetCore().QuotePrefixTableName(getTableNameFromOrmTag(object)) + m.tablesInit = m.db.GetCore().QuotePrefixTableName( + getTableNameFromOrmTag(object), + ) + m.tables = m.tablesInit return model } model.withArray = append(model.withArray, object) @@ -163,6 +166,10 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { // doWithScanStructs handles model association operations feature for struct slice. // Also see doWithScanStruct. func (m *Model) doWithScanStructs(pointer interface{}) error { + if v, ok := pointer.(reflect.Value); ok { + pointer = v.Interface() + } + var ( err error allowedTypeStrArray = make([]string, 0) diff --git a/database/gdb/gdb_result.go b/database/gdb/gdb_result.go index acfbd3f1c..63134bb09 100644 --- a/database/gdb/gdb_result.go +++ b/database/gdb/gdb_result.go @@ -33,7 +33,10 @@ func (r *SqlResult) MustGetInsertId() int64 { return id } -// see sql.Result.RowsAffected +// RowsAffected returns the number of rows affected by an +// update, insert, or delete. Not every database or database +// driver may support this. +// Also See sql.Result. func (r *SqlResult) RowsAffected() (int64, error) { if r.affected > 0 { return r.affected, nil @@ -44,7 +47,12 @@ func (r *SqlResult) RowsAffected() (int64, error) { return r.result.RowsAffected() } -// see sql.Result.LastInsertId +// LastInsertId returns the integer generated by the database +// in response to a command. Typically this will be from an +// "auto increment" column when inserting a new row. Not all +// databases support this feature, and the syntax of such +// statements varies. +// Also See sql.Result. func (r *SqlResult) LastInsertId() (int64, error) { if r.result == nil { return 0, nil diff --git a/database/gdb/gdb_type_record.go b/database/gdb/gdb_type_record.go index a660ec4ef..eff35a1f4 100644 --- a/database/gdb/gdb_type_record.go +++ b/database/gdb/gdb_type_record.go @@ -14,6 +14,11 @@ import ( "github.com/gogf/gf/util/gconv" ) +// Interface converts and returns `r` as type of interface{}. +func (r Record) Interface() interface{} { + return r +} + // Json converts `r` to JSON format content. func (r Record) Json() string { content, _ := gparser.VarToJson(r.Map()) diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index 489e3bc47..dd2764880 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -13,6 +13,11 @@ import ( "math" ) +// Interface converts and returns `r` as type of interface{}. +func (r Result) Interface() interface{} { + return r +} + // IsEmpty checks and returns whether `r` is empty. func (r Result) IsEmpty() bool { return r.Len() == 0 diff --git a/database/gdb/gdb_type_result_scanlist.go b/database/gdb/gdb_type_result_scanlist.go index 9eed29c13..18887471e 100644 --- a/database/gdb/gdb_type_result_scanlist.go +++ b/database/gdb/gdb_type_result_scanlist.go @@ -42,21 +42,21 @@ import ( // // See the example or unit testing cases for clear understanding for this function. func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) { - if r.IsEmpty() { + return doScanList(nil, r, listPointer, bindToAttrName, relationKV...) +} + +// doScanList converts `result` to struct slice which contains other complex struct attributes recursively. +// The parameter `model` is used for recursively scanning purpose, which means, it can scans the attribute struct/structs recursively but +// it needs the Model for database accessing. +// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct. +func doScanList(model *Model, result Result, listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) { + if result.IsEmpty() { return nil } // Necessary checks for parameters. if bindToAttrName == "" { return gerror.New(`bindToAttrName should not be empty`) } - //if len(relation) > 0 { - // if len(relation) < 2 { - // return gerror.New(`relation name and key should are both necessary`) - // } - // if relation[0] == "" || relation[1] == "" { - // return gerror.New(`relation name and key should not be empty`) - // } - //} var ( reflectValue = reflect.ValueOf(listPointer) @@ -67,14 +67,14 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio reflectKind = reflectValue.Kind() } if reflectKind != reflect.Ptr { - return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) + return gerror.Newf("listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind) } reflectValue = reflectValue.Elem() reflectKind = reflectValue.Kind() if reflectKind != reflect.Slice && reflectKind != reflect.Array { - return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) + return gerror.Newf("listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind) } - length := len(r) + length := len(result) if length == 0 { // The pointed slice is not empty. if reflectValue.Len() > 0 { @@ -134,7 +134,7 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio // uid:UserId relationResultFieldName = array[0] relationBindToSubAttrName = array[1] - if key, _ := gutil.MapPossibleItemByKey(r[0].Map(), relationResultFieldName); key == "" { + if key, _ := gutil.MapPossibleItemByKey(result[0].Map(), relationResultFieldName); key == "" { return gerror.Newf( `cannot find possible related table field name "%s" from given relation key "%s"`, relationResultFieldName, @@ -147,7 +147,8 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio return gerror.New(`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`) } if relationResultFieldName != "" { - relationDataMap = r.MapKeyValue(relationResultFieldName) + // Note that the value might be type of slice. + relationDataMap = result.MapKeyValue(relationResultFieldName) } if len(relationDataMap) == 0 { return gerror.Newf(`cannot find the relation data map, maybe invalid relation given "%v"`, relationKV) @@ -239,12 +240,19 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio if len(relationDataMap) > 0 { relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName) if relationFromAttrField.IsValid() { - if err = gconv.Structs( - relationDataMap[gconv.String(relationFromAttrField.Interface())], - bindToAttrValue.Addr(), - ); err != nil { + results := make(Result, 0) + for _, v := range relationDataMap[gconv.String(relationFromAttrField.Interface())].Slice() { + results = append(results, v.(Record)) + } + if err = results.Structs(bindToAttrValue.Addr()); err != nil { return err } + // Recursively Scan. + if model != nil { + if err = model.doWithScanStructs(bindToAttrValue.Addr()); err != nil { + return nil + } + } } else { // May be the attribute does not exist yet. return gerror.Newf(`invalid relation specified: "%v"`, relationKV) @@ -268,24 +276,36 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio // There's no relational data. continue } - if err = gconv.Struct(v, element); err != nil { - return err + if v.IsSlice() { + if err = v.Slice()[0].(Record).Struct(element); err != nil { + return err + } + } else { + if err = v.Val().(Record).Struct(element); err != nil { + return err + } } } else { // May be the attribute does not exist yet. return gerror.Newf(`invalid relation specified: "%v"`, relationKV) } } else { - if i >= len(r) { + if i >= len(result) { // There's no relational data. continue } - v := r[i] + v := result[i] if v == nil { // There's no relational data. continue } - if err = gconv.Struct(v, element); err != nil { + if err = v.Struct(element); err != nil { + return err + } + } + // Recursively Scan. + if model != nil { + if err = model.doWithScanStruct(element); err != nil { return err } } @@ -300,24 +320,36 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio // There's no relational data. continue } - if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil { - return err + if relationDataItem.IsSlice() { + if err = relationDataItem.Slice()[0].(Record).Struct(bindToAttrValue); err != nil { + return err + } + } else { + if err = relationDataItem.Val().(Record).Struct(bindToAttrValue); err != nil { + return err + } } } else { // May be the attribute does not exist yet. return gerror.Newf(`invalid relation specified: "%v"`, relationKV) } } else { - if i >= len(r) { + if i >= len(result) { // There's no relational data. continue } - relationDataItem := r[i] + relationDataItem := result[i] if relationDataItem == nil { // There's no relational data. continue } - if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil { + if err = relationDataItem.Struct(bindToAttrValue); err != nil { + return err + } + } + // Recursively Scan. + if model != nil { + if err = model.doWithScanStruct(bindToAttrValue); err != nil { return err } } diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index 43a0f3ab7..94827ac43 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -8,8 +8,11 @@ package gdb_test import ( "fmt" + "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gmeta" "testing" ) @@ -782,6 +785,122 @@ PRIMARY KEY (id) }) } +func Test_Table_Relation_WithAll_Embedded_WithoutMeta(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetailBase struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserDetail struct { + UserDetailBase + } + + type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) { var ( tableUser = "user" @@ -1189,3 +1308,232 @@ PRIMARY KEY (id) t.Assert(user.UserDetail.UserScores[4].Score, 5) }) } + +func Test_Table_Relation_With_MultipleDepends1(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + TableC *TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + TableB *TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.AssertNE(tableA.TableB, nil) + t.AssertNE(tableA.TableB.TableC, nil) + t.Assert(tableA.TableB.TableAId, 1) + t.Assert(tableA.TableB.TableC.Id, 100) + t.Assert(tableA.TableB.TableC.TableBId, 10) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + t.AssertNE(tableA[0].TableB, nil) + t.AssertNE(tableA[1].TableB, nil) + t.AssertNE(tableA[0].TableB.TableC, nil) + t.AssertNE(tableA[1].TableB.TableC, nil) + + t.Assert(tableA[0].Id, 1) + t.Assert(tableA[0].TableB.Id, 10) + t.Assert(tableA[0].TableB.TableC.Id, 100) + + t.Assert(tableA[1].Id, 2) + t.Assert(tableA[1].TableB.Id, 20) + t.Assert(tableA[1].TableB.TableC.Id, 300) + }) +} +func Test_Table_Relation_With_MultipleDepends2(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + TableC []*TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + TableB []*TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.Assert(len(tableA.TableB), 2) + t.Assert(tableA.TableB[0].Id, 10) + t.Assert(tableA.TableB[1].Id, 30) + + t.Assert(len(tableA.TableB[0].TableC), 2) + t.Assert(len(tableA.TableB[1].TableC), 1) + t.Assert(tableA.TableB[0].TableC[0].Id, 100) + t.Assert(tableA.TableB[0].TableC[0].TableBId, 10) + t.Assert(tableA.TableB[0].TableC[1].Id, 200) + t.Assert(tableA.TableB[0].TableC[1].TableBId, 10) + t.Assert(tableA.TableB[1].TableC[0].Id, 400) + t.Assert(tableA.TableB[1].TableC[0].TableBId, 30) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + + t.Assert(len(tableA[0].TableB), 2) + t.Assert(tableA[0].TableB[0].Id, 10) + t.Assert(tableA[0].TableB[1].Id, 30) + + t.Assert(len(tableA[0].TableB[0].TableC), 2) + t.Assert(len(tableA[0].TableB[1].TableC), 1) + t.Assert(tableA[0].TableB[0].TableC[0].Id, 100) + t.Assert(tableA[0].TableB[0].TableC[0].TableBId, 10) + t.Assert(tableA[0].TableB[0].TableC[1].Id, 200) + t.Assert(tableA[0].TableB[0].TableC[1].TableBId, 10) + t.Assert(tableA[0].TableB[1].TableC[0].Id, 400) + t.Assert(tableA[0].TableB[1].TableC[0].TableBId, 30) + + t.Assert(tableA[1].TableB[0].TableC[0].Id, 300) + t.Assert(tableA[1].TableB[0].TableC[0].TableBId, 20) + + t.Assert(tableA[1].TableB[1].Id, 40) + t.Assert(tableA[1].TableB[1].TableAId, 2) + t.Assert(tableA[1].TableB[1].TableC, nil) + }) +} +func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + *TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + *TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.AssertNE(tableA.TableB, nil) + t.AssertNE(tableA.TableB.TableC, nil) + t.Assert(tableA.TableB.TableAId, 1) + t.Assert(tableA.TableB.TableC.Id, 100) + t.Assert(tableA.TableB.TableC.TableBId, 10) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + t.AssertNE(tableA[0].TableB, nil) + t.AssertNE(tableA[1].TableB, nil) + t.AssertNE(tableA[0].TableB.TableC, nil) + t.AssertNE(tableA[1].TableB.TableC, nil) + + t.Assert(tableA[0].Id, 1) + t.Assert(tableA[0].TableB.Id, 10) + t.Assert(tableA[0].TableB.TableC.Id, 100) + + t.Assert(tableA[1].Id, 2) + t.Assert(tableA[1].TableB.Id, 20) + t.Assert(tableA[1].TableB.TableC.Id, 300) + }) +} diff --git a/database/gdb/gdb_z_mysql_method_test.go b/database/gdb/gdb_z_mysql_method_test.go index 5ac4e2730..b571a1da3 100644 --- a/database/gdb/gdb_z_mysql_method_test.go +++ b/database/gdb/gdb_z_mysql_method_test.go @@ -1436,7 +1436,7 @@ func Test_DB_UpdateCounter(t *testing.T) { gtest.C(t, func(t *gtest.T) { gdbCounter := &gdb.Counter{ - Field: "views", + Field: "id", Value: 1, } updateData := g.Map{ @@ -1449,7 +1449,7 @@ func Test_DB_UpdateCounter(t *testing.T) { one, err := db.Model(tableName).Where("id", 1).One() t.AssertNil(err) t.Assert(one["id"].Int(), 1) - t.Assert(one["views"].Int(), 1) + t.Assert(one["views"].Int(), 2) }) gtest.C(t, func(t *gtest.T) { @@ -1468,7 +1468,7 @@ func Test_DB_UpdateCounter(t *testing.T) { one, err := db.Model(tableName).Where("id", 1).One() t.AssertNil(err) t.Assert(one["id"].Int(), 1) - t.Assert(one["views"].Int(), 0) + t.Assert(one["views"].Int(), 1) }) } diff --git a/database/gdb/testdata/with_multiple_depends.sql b/database/gdb/testdata/with_multiple_depends.sql new file mode 100644 index 000000000..f4327b947 --- /dev/null +++ b/database/gdb/testdata/with_multiple_depends.sql @@ -0,0 +1,33 @@ + +CREATE TABLE `table_a` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_a` VALUES (1, 'table_a_test1'); +INSERT INTO `table_a` VALUES (2, 'table_a_test2'); + +CREATE TABLE `table_b` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_a_id` int(11) NOT NULL, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_b` VALUES (10, 1, 'table_b_test1'); +INSERT INTO `table_b` VALUES (20, 2, 'table_b_test2'); +INSERT INTO `table_b` VALUES (30, 1, 'table_b_test3'); +INSERT INTO `table_b` VALUES (40, 2, 'table_b_test4'); + +CREATE TABLE `table_c` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_b_id` int(11) NOT NULL, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_c` VALUES (100, 10, 'table_c_test1'); +INSERT INTO `table_c` VALUES (200, 10, 'table_c_test2'); +INSERT INTO `table_c` VALUES (300, 20, 'table_c_test3'); +INSERT INTO `table_c` VALUES (400, 30, 'table_c_test4'); \ No newline at end of file diff --git a/encoding/gjson/gjson.go b/encoding/gjson/gjson.go index 573acce39..887b0f232 100644 --- a/encoding/gjson/gjson.go +++ b/encoding/gjson/gjson.go @@ -8,6 +8,7 @@ package gjson import ( + "github.com/gogf/gf/internal/utils" "reflect" "strconv" "strings" @@ -37,11 +38,23 @@ type Options struct { StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64. } +// apiInterface is used for type assert api for Interface(). +type apiInterface interface { + Interface() interface{} +} + // setValue sets to by . // Note: // 1. If value is nil and removed is true, means deleting this value; // 2. It's quite complicated in hierarchical data search, node creating and data assignment; func (j *Json) setValue(pattern string, value interface{}, removed bool) error { + if value != nil { + if utils.IsStruct(value) { + if v, ok := value.(apiInterface); ok { + value = v.Interface() + } + } + } array := strings.Split(pattern, string(j.c)) length := len(array) value = j.convertValue(value) diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index a45f6a6de..642990201 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -18,7 +18,13 @@ import ( ) // Value returns the json value. +// Deprecated, use Interface instead. func (j *Json) Value() interface{} { + return j.Interface() +} + +// Interface returns the json value. +func (j *Json) Interface() interface{} { j.mu.RLock() defer j.mu.RUnlock() return *(j.p) diff --git a/encoding/gjson/gjson_z_unit_basic_test.go b/encoding/gjson/gjson_z_unit_basic_test.go index 35b9538f0..7013f2fbf 100644 --- a/encoding/gjson/gjson_z_unit_basic_test.go +++ b/encoding/gjson/gjson_z_unit_basic_test.go @@ -485,3 +485,17 @@ func TestJson_IsNil(t *testing.T) { t.Assert(j.IsNil(), true) }) } + +func TestJson_Set_With_Struct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gjson.New(g.Map{ + "user1": g.Map{"name": "user1"}, + "user2": g.Map{"name": "user2"}, + "user3": g.Map{"name": "user3"}, + }) + user1 := v.GetJson("user1") + user1.Set("id", 111) + v.Set("user1", user1) + t.Assert(v.Get("user1.id"), 111) + }) +} diff --git a/internal/intlog/intlog.go b/internal/intlog/intlog.go index d5ca85d6a..a22207ccf 100644 --- a/internal/intlog/intlog.go +++ b/internal/intlog/intlog.go @@ -71,8 +71,9 @@ func doPrint(ctx context.Context, content string, stack bool) { buffer.WriteString(now()) buffer.WriteString(" [INTE] ") buffer.WriteString(file()) + buffer.WriteString(" ") if s := traceIdStr(ctx); s != "" { - buffer.WriteString(" " + s) + buffer.WriteString(s + " ") } buffer.WriteString(content) buffer.WriteString("\n") diff --git a/internal/utils/utils_is.go b/internal/utils/utils_is.go new file mode 100644 index 000000000..7e5757a8d --- /dev/null +++ b/internal/utils/utils_is.go @@ -0,0 +1,98 @@ +// 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 utils + +import ( + "github.com/gogf/gf/internal/empty" + "reflect" +) + +// IsNil checks whether `value` is nil. +func IsNil(value interface{}) bool { + return value == nil +} + +// IsEmpty checks whether `value` is empty. +func IsEmpty(value interface{}) bool { + return empty.IsEmpty(value) +} + +// IsInt checks whether `value` is type of int. +func IsInt(value interface{}) bool { + switch value.(type) { + case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64: + return true + } + return false +} + +// IsUint checks whether `value` is type of uint. +func IsUint(value interface{}) bool { + switch value.(type) { + case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64: + return true + } + return false +} + +// IsFloat checks whether `value` is type of float. +func IsFloat(value interface{}) bool { + switch value.(type) { + case float32, *float32, float64, *float64: + return true + } + return false +} + +// IsSlice checks whether `value` is type of slice. +func IsSlice(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + return true + } + return false +} + +// IsMap checks whether `value` is type of map. +func IsMap(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Map: + return true + } + return false +} + +// IsStruct checks whether `value` is type of struct. +func IsStruct(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Struct: + return true + } + return false +} diff --git a/internal/utils/utils_z_is_test.go b/internal/utils/utils_z_is_test.go new file mode 100644 index 000000000..77df365c4 --- /dev/null +++ b/internal/utils/utils_z_is_test.go @@ -0,0 +1,171 @@ +// 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 utils_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func TestVar_IsNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsNil(0), false) + t.Assert(utils.IsNil(nil), true) + t.Assert(utils.IsNil(g.Map{}), false) + t.Assert(utils.IsNil(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsNil(1), false) + t.Assert(utils.IsNil(0.1), false) + t.Assert(utils.IsNil(g.Map{"k": "v"}), false) + t.Assert(utils.IsNil(g.Slice{0}), false) + }) +} + +func TestVar_IsEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsEmpty(0), true) + t.Assert(utils.IsEmpty(nil), true) + t.Assert(utils.IsEmpty(g.Map{}), true) + t.Assert(utils.IsEmpty(g.Slice{}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsEmpty(1), false) + t.Assert(utils.IsEmpty(0.1), false) + t.Assert(utils.IsEmpty(g.Map{"k": "v"}), false) + t.Assert(utils.IsEmpty(g.Slice{0}), false) + }) +} + +func TestVar_IsInt(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(0), true) + t.Assert(utils.IsInt(nil), false) + t.Assert(utils.IsInt(g.Map{}), false) + t.Assert(utils.IsInt(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(1), true) + t.Assert(utils.IsInt(-1), true) + t.Assert(utils.IsInt(0.1), false) + t.Assert(utils.IsInt(g.Map{"k": "v"}), false) + t.Assert(utils.IsInt(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(int8(1)), true) + t.Assert(utils.IsInt(uint8(1)), false) + }) +} + +func TestVar_IsUint(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(0), false) + t.Assert(utils.IsUint(nil), false) + t.Assert(utils.IsUint(g.Map{}), false) + t.Assert(utils.IsUint(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(1), false) + t.Assert(utils.IsUint(-1), false) + t.Assert(utils.IsUint(0.1), false) + t.Assert(utils.IsUint(g.Map{"k": "v"}), false) + t.Assert(utils.IsUint(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(int8(1)), false) + t.Assert(utils.IsUint(uint8(1)), true) + }) +} + +func TestVar_IsFloat(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(0), false) + t.Assert(utils.IsFloat(nil), false) + t.Assert(utils.IsFloat(g.Map{}), false) + t.Assert(utils.IsFloat(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(1), false) + t.Assert(utils.IsFloat(-1), false) + t.Assert(utils.IsFloat(0.1), true) + t.Assert(utils.IsFloat(float64(1)), true) + t.Assert(utils.IsFloat(g.Map{"k": "v"}), false) + t.Assert(utils.IsFloat(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(int8(1)), false) + t.Assert(utils.IsFloat(uint8(1)), false) + }) +} + +func TestVar_IsSlice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(0), false) + t.Assert(utils.IsSlice(nil), false) + t.Assert(utils.IsSlice(g.Map{}), false) + t.Assert(utils.IsSlice(g.Slice{}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(1), false) + t.Assert(utils.IsSlice(-1), false) + t.Assert(utils.IsSlice(0.1), false) + t.Assert(utils.IsSlice(float64(1)), false) + t.Assert(utils.IsSlice(g.Map{"k": "v"}), false) + t.Assert(utils.IsSlice(g.Slice{0}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(int8(1)), false) + t.Assert(utils.IsSlice(uint8(1)), false) + }) +} + +func TestVar_IsMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(0), false) + t.Assert(utils.IsMap(nil), false) + t.Assert(utils.IsMap(g.Map{}), true) + t.Assert(utils.IsMap(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(1), false) + t.Assert(utils.IsMap(-1), false) + t.Assert(utils.IsMap(0.1), false) + t.Assert(utils.IsMap(float64(1)), false) + t.Assert(utils.IsMap(g.Map{"k": "v"}), true) + t.Assert(utils.IsMap(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(int8(1)), false) + t.Assert(utils.IsMap(uint8(1)), false) + }) +} + +func TestVar_IsStruct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsStruct(0), false) + t.Assert(utils.IsStruct(nil), false) + t.Assert(utils.IsStruct(g.Map{}), false) + t.Assert(utils.IsStruct(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsStruct(1), false) + t.Assert(utils.IsStruct(-1), false) + t.Assert(utils.IsStruct(0.1), false) + t.Assert(utils.IsStruct(float64(1)), false) + t.Assert(utils.IsStruct(g.Map{"k": "v"}), false) + t.Assert(utils.IsStruct(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + a := &struct { + }{} + t.Assert(utils.IsStruct(a), true) + t.Assert(utils.IsStruct(*a), true) + t.Assert(utils.IsStruct(&a), true) + }) +} diff --git a/net/ghttp/ghttp_response_view.go b/net/ghttp/ghttp_response_view.go index 194d04c0d..47da241dc 100644 --- a/net/ghttp/ghttp_response_view.go +++ b/net/ghttp/ghttp_response_view.go @@ -10,6 +10,7 @@ package ghttp import ( "github.com/gogf/gf/os/gcfg" "github.com/gogf/gf/os/gview" + "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gmode" "github.com/gogf/gf/util/gutil" ) @@ -62,7 +63,7 @@ func (r *Response) ParseTpl(tpl string, params ...gview.Params) (string, error) return r.Request.GetView().Parse(r.Request.Context(), tpl, r.buildInVars(params...)) } -// ParseDefault parses the default template file with params. +// ParseTplDefault parses the default template file with params. func (r *Response) ParseTplDefault(params ...gview.Params) (string, error) { return r.Request.GetView().ParseDefault(r.Request.Context(), r.buildInVars(params...)) } @@ -81,17 +82,18 @@ func (r *Response) buildInVars(params ...map[string]interface{}) map[string]inte gutil.MapMerge(m, params[0]) } // Retrieve custom template variables from request object. + sessionMap := gconv.MapDeep(r.Request.Session.Map()) gutil.MapMerge(m, map[string]interface{}{ "Form": r.Request.GetFormMap(), "Query": r.Request.GetQueryMap(), "Request": r.Request.GetMap(), "Cookie": r.Request.Cookie.Map(), - "Session": r.Request.Session.Map(), + "Session": sessionMap, }) // Note that it should assign no Config variable to template // if there's no configuration file. if c := gcfg.Instance(); c.Available() { - m["Config"] = c.GetMap(".") + m["Config"] = c.Map() } return m } diff --git a/net/gtrace/gtrace_baggage.go b/net/gtrace/gtrace_baggage.go index 28a175c20..d04834645 100644 --- a/net/gtrace/gtrace_baggage.go +++ b/net/gtrace/gtrace_baggage.go @@ -10,7 +10,7 @@ import ( "context" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/container/gvar" - "go.opentelemetry.io/otel/attribute" + "github.com/gogf/gf/util/gconv" "go.opentelemetry.io/otel/baggage" ) @@ -37,39 +37,37 @@ func (b *Baggage) Ctx() context.Context { // SetValue is a convenient function for adding one key-value pair to baggage. // Note that it uses attribute.Any to set the key-value pair. func (b *Baggage) SetValue(key string, value interface{}) context.Context { - b.ctx = baggage.ContextWithValues(b.ctx, attribute.Any(key, value)) + member, _ := baggage.NewMember(key, gconv.String(value)) + bag, _ := baggage.New(member) + b.ctx = baggage.ContextWithBaggage(b.ctx, bag) return b.ctx } // SetMap is a convenient function for adding map key-value pairs to baggage. // Note that it uses attribute.Any to set the key-value pair. func (b *Baggage) SetMap(data map[string]interface{}) context.Context { - pairs := make([]attribute.KeyValue, 0) + members := make([]baggage.Member, 0) for k, v := range data { - pairs = append(pairs, attribute.Any(k, v)) + member, _ := baggage.NewMember(k, gconv.String(v)) + members = append(members, member) } - b.ctx = baggage.ContextWithValues(b.ctx, pairs...) + bag, _ := baggage.New(members...) + b.ctx = baggage.ContextWithBaggage(b.ctx, bag) return b.ctx } // GetMap retrieves and returns the baggage values as map. func (b *Baggage) GetMap() *gmap.StrAnyMap { m := gmap.NewStrAnyMap() - set := baggage.Set(b.ctx) - if length := set.Len(); length > 0 { - if length == 0 { - return m - } - inter := set.Iter() - for inter.Next() { - m.Set(string(inter.Label().Key), inter.Label().Value.AsInterface()) - } + members := baggage.FromContext(b.ctx).Members() + for i := range members { + m.Set(members[i].Key(), members[i].Value()) } return m } // GetVar retrieves value and returns a *gvar.Var for specified key from baggage. func (b *Baggage) GetVar(key string) *gvar.Var { - value := baggage.Value(b.ctx, attribute.Key(key)) - return gvar.New(value.AsInterface()) + value := baggage.FromContext(b.ctx).Member(key).Value() + return gvar.New(value) } diff --git a/net/gtrace/gtrace_span.go b/net/gtrace/gtrace_span.go index f9163a60f..72f88c418 100644 --- a/net/gtrace/gtrace_span.go +++ b/net/gtrace/gtrace_span.go @@ -16,7 +16,7 @@ type Span struct { } // NewSpan creates a span using default tracer. -func NewSpan(ctx context.Context, spanName string, opts ...trace.SpanOption) (context.Context, *Span) { +func NewSpan(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, *Span) { ctx, span := NewTracer().Start(ctx, spanName, opts...) return ctx, &Span{ Span: span, diff --git a/net/gtrace/gtrace_unit_carrier_test.go b/net/gtrace/gtrace_unit_carrier_test.go index f67e85ae0..a070b2ab4 100644 --- a/net/gtrace/gtrace_unit_carrier_test.go +++ b/net/gtrace/gtrace_unit_carrier_test.go @@ -57,7 +57,7 @@ func TestNewCarrier(t *testing.T) { t.Assert(carrier1.String(), `{"traceparent":"00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000002-01","tracestate":""}`) ctx = otel.GetTextMapPropagator().Extract(ctx, carrier1) - gotSc := trace.RemoteSpanContextFromContext(ctx) + gotSc := trace.SpanContextFromContext(ctx) t.Assert(gotSc.TraceID().String(), traceID.String()) t.Assert(gotSc.SpanID().String(), "0000000000000002") }) diff --git a/os/gfsnotify/gfsnotify.go b/os/gfsnotify/gfsnotify.go index 0224b8ba0..9c2f5864f 100644 --- a/os/gfsnotify/gfsnotify.go +++ b/os/gfsnotify/gfsnotify.go @@ -96,8 +96,8 @@ func New() (*Watcher, error) { return w, nil } -// Add monitors using default watcher with callback function . -// The optional parameter specifies whether monitoring the recursively, which is true in default. +// Add monitors `path` using default watcher with callback function `callbackFunc`. +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default. func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { w, err := getDefaultWatcher() if err != nil { @@ -106,11 +106,11 @@ func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callb return w.Add(path, callbackFunc, recursive...) } -// AddOnce monitors using default watcher with callback function only once using unique name . -// If AddOnce is called multiple times with the same parameter, is only added to monitor once. It returns error -// if it's called twice with the same . +// AddOnce monitors `path` using default watcher with callback function `callbackFunc` only once using unique name `name`. +// If AddOnce is called multiple times with the same `name` parameter, `path` is only added to monitor once. It returns error +// if it's called twice with the same `name`. // -// The optional parameter specifies whether monitoring the recursively, which is true in default. +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default. func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { w, err := getDefaultWatcher() if err != nil { @@ -119,7 +119,7 @@ func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bo return w.AddOnce(name, path, callbackFunc, recursive...) } -// Remove removes all monitoring callbacks of given from watcher recursively. +// Remove removes all monitoring callbacks of given `path` from watcher recursively. func Remove(path string) error { w, err := getDefaultWatcher() if err != nil { diff --git a/os/gfsnotify/gfsnotify_filefunc.go b/os/gfsnotify/gfsnotify_filefunc.go index f1d4daef6..3be6fbde8 100644 --- a/os/gfsnotify/gfsnotify_filefunc.go +++ b/os/gfsnotify/gfsnotify_filefunc.go @@ -24,7 +24,7 @@ func fileDir(path string) string { return filepath.Dir(path) } -// fileRealPath converts the given to its absolute path +// fileRealPath converts the given `path` to its absolute path // and checks if the file path exists. // If the file does not exist, return an empty string. func fileRealPath(path string) string { @@ -38,7 +38,7 @@ func fileRealPath(path string) string { return p } -// fileExists checks whether given exist. +// fileExists checks whether given `path` exist. func fileExists(path string) bool { if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) { return true @@ -46,7 +46,7 @@ func fileExists(path string) bool { return false } -// fileIsDir checks whether given a directory. +// fileIsDir checks whether given `path` a directory. func fileIsDir(path string) bool { s, err := os.Stat(path) if err != nil { @@ -55,7 +55,7 @@ func fileIsDir(path string) bool { return s.IsDir() } -// fileAllDirs returns all sub-folders including itself of given recursively. +// fileAllDirs returns all sub-folders including itself of given `path` recursively. func fileAllDirs(path string) (list []string) { list = []string{path} file, err := os.Open(path) @@ -78,8 +78,8 @@ func fileAllDirs(path string) (list []string) { return } -// fileScanDir returns all sub-files with absolute paths of given , -// It scans directory recursively if given parameter is true. +// fileScanDir returns all sub-files with absolute paths of given `path`, +// It scans directory recursively if given parameter `recursive` is true. func fileScanDir(path string, pattern string, recursive ...bool) ([]string, error) { list, err := doFileScanDir(path, pattern, recursive...) if err != nil { @@ -94,10 +94,10 @@ func fileScanDir(path string, pattern string, recursive ...bool) ([]string, erro // doFileScanDir is an internal method which scans directory // and returns the absolute path list of files that are not sorted. // -// The pattern parameter supports multiple file name patterns, +// The pattern parameter `pattern` supports multiple file name patterns, // using the ',' symbol to separate multiple patterns. // -// It scans directory recursively if given parameter is true. +// It scans directory recursively if given parameter `recursive` is true. func doFileScanDir(path string, pattern string, recursive ...bool) ([]string, error) { list := ([]string)(nil) file, err := os.Open(path) diff --git a/os/gfsnotify/gfsnotify_watcher.go b/os/gfsnotify/gfsnotify_watcher.go index 3219c26c8..777ada43a 100644 --- a/os/gfsnotify/gfsnotify_watcher.go +++ b/os/gfsnotify/gfsnotify_watcher.go @@ -14,19 +14,20 @@ import ( "github.com/gogf/gf/container/glist" ) -// Add monitors with callback function to the watcher. -// The optional parameter specifies whether monitoring the recursively, +// Add monitors `path` with callback function `callbackFunc` to the watcher. +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { return w.AddOnce("", path, callbackFunc, recursive...) } -// AddOnce monitors with callback function only once using unique name -// to the watcher. If AddOnce is called multiple times with the same parameter, -// is only added to monitor once. -// It returns error if it's called twice with the same . +// AddOnce monitors `path` with callback function `callbackFunc` only once using unique name +// `name` to the watcher. If AddOnce is called multiple times with the same `name` parameter, +// `path` is only added to monitor once. // -// The optional parameter specifies whether monitoring the recursively, +// It returns error if it's called twice with the same `name`. +// +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { w.nameSet.AddIfNotExistFuncLock(name, func() bool { @@ -99,8 +100,6 @@ func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event } // Add the callback to global callback map. callbackIdMap.Set(callback.Id, callback) - - //intlog.Print("addWithCallbackFunc", name, path, callback.recursive) return } @@ -113,7 +112,7 @@ func (w *Watcher) Close() { close(w.closeChan) } -// Remove removes monitor and all callbacks associated with the recursively. +// Remove removes monitor and all callbacks associated with the `path` recursively. func (w *Watcher) Remove(path string) error { // Firstly remove the callbacks of the path. if r := w.callbacks.Remove(path); r != nil { diff --git a/os/gfsnotify/gfsnotify_watcher_loop.go b/os/gfsnotify/gfsnotify_watcher_loop.go index 2618239ce..6eb1e1a7d 100644 --- a/os/gfsnotify/gfsnotify_watcher_loop.go +++ b/os/gfsnotify/gfsnotify_watcher_loop.go @@ -134,7 +134,7 @@ func (w *Watcher) eventLoop() { }() } -// getCallbacks searches and returns all callbacks with given . +// getCallbacks searches and returns all callbacks with given `path`. // It also searches its parents for callbacks if they're recursive. func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { // Firstly add the callbacks of itself.