// 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 ( "time" "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/internal/empty" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/text/gregex" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/gutil" ) // QuoteWord checks given string `s` a word, // if true it quotes `s` with security chars of the database // and returns the quoted string; or else it returns `s` without any change. // // The meaning of a `word` can be considered as a column name. func (m *Model) QuoteWord(s string) string { return m.db.GetCore().QuoteWord(s) } // TableFields retrieves and returns the fields' information of specified table of current // schema. // // Also see DriverMysql.TableFields. func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) { var ( ctx = m.GetCtx() usedTable = m.db.GetCore().guessPrimaryTableName(tableStr) usedSchema = gutil.GetOrDefaultStr(m.schema, schema...) ) // Sharding feature. usedSchema, err = m.getActualSchema(ctx, usedSchema) if err != nil { return nil, err } usedTable, err = m.getActualTable(ctx, usedTable) if err != nil { return nil, err } return m.db.TableFields(ctx, usedTable, usedSchema) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns // the current model. func (m *Model) getModel() *Model { if !m.safe { return m } else { return m.Clone() } } // mappingAndFilterToTableFields mappings and changes given field name to really table field name. // Eg: // ID -> id // NICK_Name -> nickname. func (m *Model) mappingAndFilterToTableFields(table string, fields []any, filter bool) []any { var fieldsTable = table if fieldsTable != "" { hasTable, _ := m.db.GetCore().HasTable(fieldsTable) if !hasTable { fieldsTable = m.tablesInit } } if fieldsTable == "" { fieldsTable = m.tablesInit } fieldsMap, _ := m.TableFields(fieldsTable) if len(fieldsMap) == 0 { return fields } var outputFieldsArray = make([]any, 0) fieldsKeyMap := make(map[string]interface{}, len(fieldsMap)) for k := range fieldsMap { fieldsKeyMap[k] = nil } for _, field := range fields { var ( fieldStr = gconv.String(field) inputFieldsArray []string ) switch { case gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, fieldStr): inputFieldsArray = append(inputFieldsArray, fieldStr) case gregex.IsMatchString(regularFieldNameWithCommaRegPattern, fieldStr): inputFieldsArray = gstr.SplitAndTrim(fieldStr, ",") default: // Example: // user.id, user.name // replace(concat_ws(',',lpad(s.id, 6, '0'),s.name),',','') `code` outputFieldsArray = append(outputFieldsArray, field) continue } for _, inputField := range inputFieldsArray { if !gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, inputField) { outputFieldsArray = append(outputFieldsArray, inputField) continue } if _, ok := fieldsKeyMap[inputField]; !ok { // Example: // id, name if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, inputField); foundKey != "" { outputFieldsArray = append(outputFieldsArray, foundKey) } else if !filter { outputFieldsArray = append(outputFieldsArray, inputField) } } else { outputFieldsArray = append(outputFieldsArray, inputField) } } } return outputFieldsArray } // filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations. // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) { var err error switch value := data.(type) { case List: var omitEmpty bool if m.option&optionOmitNilDataList > 0 { omitEmpty = true } for k, item := range value { value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, omitEmpty) if err != nil { return nil, err } } return value, nil case Map: return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true) default: return data, nil } } // doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map. // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) { var ( err error ctx = m.GetCtx() core = m.db.GetCore() schema = m.schema table = m.tablesInit ) // Sharding feature. schema, err = m.getActualSchema(ctx, schema) if err != nil { return nil, err } table, err = m.getActualTable(ctx, table) if err != nil { return nil, err } data, err = core.mappingAndFilterData( ctx, schema, table, data, m.filter, ) if err != nil { return nil, err } // Remove key-value pairs of which the value is nil. if allowOmitEmpty && m.option&optionOmitNilData > 0 { tempMap := make(Map, len(data)) for k, v := range data { if empty.IsNil(v) { continue } tempMap[k] = v } data = tempMap } // Remove key-value pairs of which the value is empty. if allowOmitEmpty && m.option&optionOmitEmptyData > 0 { tempMap := make(Map, len(data)) for k, v := range data { if empty.IsEmpty(v) { continue } // Special type filtering. switch r := v.(type) { case time.Time: if r.IsZero() { continue } case *time.Time: if r.IsZero() { continue } case gtime.Time: if r.IsZero() { continue } case *gtime.Time: if r.IsZero() { continue } } tempMap[k] = v } data = tempMap } if len(m.fields) > 0 { // Keep specified fields. var ( fieldSet = gset.NewStrSetFrom(gconv.Strings(m.fields)) charL, charR = m.db.GetChars() chars = charL + charR ) fieldSet.Walk(func(item string) string { return gstr.Trim(item, chars) }) for k := range data { k = gstr.Trim(k, chars) if !fieldSet.Contains(k) { delete(data, k) } } } else if len(m.fieldsEx) > 0 { // Filter specified fields. for _, v := range m.fieldsEx { delete(data, gconv.String(v)) } } return data, nil } // getLink returns the underlying database link object with configured `linkType` attribute. // The parameter `master` specifies whether using the master node if master-slave configured. func (m *Model) getLink(master bool) Link { if m.tx != nil { if sqlTx := m.tx.GetSqlTX(); sqlTx != nil { return &txLink{sqlTx} } } linkType := m.linkType if linkType == 0 { if master { linkType = linkTypeMaster } else { linkType = linkTypeSlave } } switch linkType { case linkTypeMaster: link, err := m.db.GetCore().MasterLink(m.schema) if err != nil { panic(err) } return link case linkTypeSlave: link, err := m.db.GetCore().SlaveLink(m.schema) if err != nil { panic(err) } return link } return nil } // getPrimaryKey retrieves and returns the primary key name of the model table. // 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.tablesInit, " ")[0] tableFields, err := m.TableFields(table) if err != nil { return "" } for name, field := range tableFields { if gstr.ContainsI(field.Key, "pri") { return name } } return "" } // mergeArguments creates and returns new arguments by merging `m.extraArgs` and given `args`. func (m *Model) mergeArguments(args []interface{}) []interface{} { if len(m.extraArgs) > 0 { newArgs := make([]interface{}, len(m.extraArgs)+len(args)) copy(newArgs, m.extraArgs) copy(newArgs[len(m.extraArgs):], args) return newArgs } return args }