github.com/wangyougui/gf/v2@v2.6.5/database/gdb/gdb_model_utility.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gdb
     8  
     9  import (
    10  	"time"
    11  
    12  	"github.com/wangyougui/gf/v2/container/gset"
    13  	"github.com/wangyougui/gf/v2/internal/empty"
    14  	"github.com/wangyougui/gf/v2/os/gtime"
    15  	"github.com/wangyougui/gf/v2/text/gregex"
    16  	"github.com/wangyougui/gf/v2/text/gstr"
    17  	"github.com/wangyougui/gf/v2/util/gutil"
    18  )
    19  
    20  // QuoteWord checks given string `s` a word,
    21  // if true it quotes `s` with security chars of the database
    22  // and returns the quoted string; or else it returns `s` without any change.
    23  //
    24  // The meaning of a `word` can be considered as a column name.
    25  func (m *Model) QuoteWord(s string) string {
    26  	return m.db.GetCore().QuoteWord(s)
    27  }
    28  
    29  // TableFields retrieves and returns the fields' information of specified table of current
    30  // schema.
    31  //
    32  // Also see DriverMysql.TableFields.
    33  func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) {
    34  	var (
    35  		table      = m.db.GetCore().guessPrimaryTableName(tableStr)
    36  		usedSchema = gutil.GetOrDefaultStr(m.schema, schema...)
    37  	)
    38  	return m.db.TableFields(m.GetCtx(), table, usedSchema)
    39  }
    40  
    41  // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
    42  // the current model.
    43  func (m *Model) getModel() *Model {
    44  	if !m.safe {
    45  		return m
    46  	} else {
    47  		return m.Clone()
    48  	}
    49  }
    50  
    51  // mappingAndFilterToTableFields mappings and changes given field name to really table field name.
    52  // Eg:
    53  // ID        -> id
    54  // NICK_Name -> nickname.
    55  func (m *Model) mappingAndFilterToTableFields(table string, fields []string, filter bool) []string {
    56  	var fieldsTable = table
    57  	if fieldsTable != "" {
    58  		hasTable, _ := m.db.GetCore().HasTable(fieldsTable)
    59  		if !hasTable {
    60  			fieldsTable = m.tablesInit
    61  		}
    62  	}
    63  	if fieldsTable == "" {
    64  		fieldsTable = m.tablesInit
    65  	}
    66  
    67  	fieldsMap, _ := m.TableFields(fieldsTable)
    68  	if len(fieldsMap) == 0 {
    69  		return fields
    70  	}
    71  	var outputFieldsArray = make([]string, 0)
    72  	fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
    73  	for k := range fieldsMap {
    74  		fieldsKeyMap[k] = nil
    75  	}
    76  	for _, field := range fields {
    77  		var inputFieldsArray []string
    78  		if gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, field) {
    79  			inputFieldsArray = append(inputFieldsArray, field)
    80  		} else if gregex.IsMatchString(regularFieldNameWithCommaRegPattern, field) {
    81  			inputFieldsArray = gstr.SplitAndTrim(field, ",")
    82  		} else {
    83  			// Example:
    84  			// user.id, user.name
    85  			// replace(concat_ws(',',lpad(s.id, 6, '0'),s.name),',','') `code`
    86  			outputFieldsArray = append(outputFieldsArray, field)
    87  			continue
    88  		}
    89  		for _, inputField := range inputFieldsArray {
    90  			if !gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, inputField) {
    91  				outputFieldsArray = append(outputFieldsArray, inputField)
    92  				continue
    93  			}
    94  			if _, ok := fieldsKeyMap[inputField]; !ok {
    95  				// Example:
    96  				// id, name
    97  				if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, inputField); foundKey != "" {
    98  					outputFieldsArray = append(outputFieldsArray, foundKey)
    99  				} else if !filter {
   100  					outputFieldsArray = append(outputFieldsArray, inputField)
   101  				}
   102  			} else {
   103  				outputFieldsArray = append(outputFieldsArray, inputField)
   104  			}
   105  		}
   106  	}
   107  	return outputFieldsArray
   108  }
   109  
   110  // filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
   111  // Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
   112  func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
   113  	var err error
   114  	switch value := data.(type) {
   115  	case List:
   116  		var omitEmpty bool
   117  		if m.option&optionOmitNilDataList > 0 {
   118  			omitEmpty = true
   119  		}
   120  		for k, item := range value {
   121  			value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, omitEmpty)
   122  			if err != nil {
   123  				return nil, err
   124  			}
   125  		}
   126  		return value, nil
   127  
   128  	case Map:
   129  		return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
   130  
   131  	default:
   132  		return data, nil
   133  	}
   134  }
   135  
   136  // doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map.
   137  // Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
   138  func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
   139  	var err error
   140  	data, err = m.db.GetCore().mappingAndFilterData(
   141  		m.GetCtx(), m.schema, m.tablesInit, data, m.filter,
   142  	)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	// Remove key-value pairs of which the value is nil.
   147  	if allowOmitEmpty && m.option&optionOmitNilData > 0 {
   148  		tempMap := make(Map, len(data))
   149  		for k, v := range data {
   150  			if empty.IsNil(v) {
   151  				continue
   152  			}
   153  			tempMap[k] = v
   154  		}
   155  		data = tempMap
   156  	}
   157  
   158  	// Remove key-value pairs of which the value is empty.
   159  	if allowOmitEmpty && m.option&optionOmitEmptyData > 0 {
   160  		tempMap := make(Map, len(data))
   161  		for k, v := range data {
   162  			if empty.IsEmpty(v) {
   163  				continue
   164  			}
   165  			// Special type filtering.
   166  			switch r := v.(type) {
   167  			case time.Time:
   168  				if r.IsZero() {
   169  					continue
   170  				}
   171  			case *time.Time:
   172  				if r.IsZero() {
   173  					continue
   174  				}
   175  			case gtime.Time:
   176  				if r.IsZero() {
   177  					continue
   178  				}
   179  			case *gtime.Time:
   180  				if r.IsZero() {
   181  					continue
   182  				}
   183  			}
   184  			tempMap[k] = v
   185  		}
   186  		data = tempMap
   187  	}
   188  
   189  	if len(m.fields) > 0 && m.fields != "*" {
   190  		// Keep specified fields.
   191  		var (
   192  			set          = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
   193  			charL, charR = m.db.GetChars()
   194  			chars        = charL + charR
   195  		)
   196  		set.Walk(func(item string) string {
   197  			return gstr.Trim(item, chars)
   198  		})
   199  		for k := range data {
   200  			k = gstr.Trim(k, chars)
   201  			if !set.Contains(k) {
   202  				delete(data, k)
   203  			}
   204  		}
   205  	} else if len(m.fieldsEx) > 0 {
   206  		// Filter specified fields.
   207  		for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") {
   208  			delete(data, v)
   209  		}
   210  	}
   211  	return data, nil
   212  }
   213  
   214  // getLink returns the underlying database link object with configured `linkType` attribute.
   215  // The parameter `master` specifies whether using the master node if master-slave configured.
   216  func (m *Model) getLink(master bool) Link {
   217  	if m.tx != nil {
   218  		return &txLink{m.tx.GetSqlTX()}
   219  	}
   220  	linkType := m.linkType
   221  	if linkType == 0 {
   222  		if master {
   223  			linkType = linkTypeMaster
   224  		} else {
   225  			linkType = linkTypeSlave
   226  		}
   227  	}
   228  	switch linkType {
   229  	case linkTypeMaster:
   230  		link, err := m.db.GetCore().MasterLink(m.schema)
   231  		if err != nil {
   232  			panic(err)
   233  		}
   234  		return link
   235  	case linkTypeSlave:
   236  		link, err := m.db.GetCore().SlaveLink(m.schema)
   237  		if err != nil {
   238  			panic(err)
   239  		}
   240  		return link
   241  	}
   242  	return nil
   243  }
   244  
   245  // getPrimaryKey retrieves and returns the primary key name of the model table.
   246  // It parses m.tables to retrieve the primary table name, supporting m.tables like:
   247  // "user", "user u", "user as u, user_detail as ud".
   248  func (m *Model) getPrimaryKey() string {
   249  	table := gstr.SplitAndTrim(m.tablesInit, " ")[0]
   250  	tableFields, err := m.TableFields(table)
   251  	if err != nil {
   252  		return ""
   253  	}
   254  	for name, field := range tableFields {
   255  		if gstr.ContainsI(field.Key, "pri") {
   256  			return name
   257  		}
   258  	}
   259  	return ""
   260  }
   261  
   262  // mergeArguments creates and returns new arguments by merging `m.extraArgs` and given `args`.
   263  func (m *Model) mergeArguments(args []interface{}) []interface{} {
   264  	if len(m.extraArgs) > 0 {
   265  		newArgs := make([]interface{}, len(m.extraArgs)+len(args))
   266  		copy(newArgs, m.extraArgs)
   267  		copy(newArgs[len(m.extraArgs):], args)
   268  		return newArgs
   269  	}
   270  	return args
   271  }