github.com/doug-martin/goqu/v9@v9.19.0/internal/util/column_map.go (about)

     1  package util
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/doug-martin/goqu/v9/internal/tag"
     9  )
    10  
    11  type (
    12  	ColumnData struct {
    13  		ColumnName     string
    14  		FieldIndex     []int
    15  		ShouldInsert   bool
    16  		ShouldUpdate   bool
    17  		DefaultIfEmpty bool
    18  		OmitNil        bool
    19  		OmitEmpty      bool
    20  		GoType         reflect.Type
    21  	}
    22  	ColumnMap map[string]ColumnData
    23  )
    24  
    25  func newColumnMap(t reflect.Type, fieldIndex []int, prefixes []string) ColumnMap {
    26  	cm, n := ColumnMap{}, t.NumField()
    27  	var subColMaps []ColumnMap
    28  	for i := 0; i < n; i++ {
    29  		f := t.Field(i)
    30  		if f.Anonymous && (f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr) {
    31  			goquTag := tag.New("db", f.Tag)
    32  			if !goquTag.Contains("-") {
    33  				subColMaps = append(subColMaps, getStructColumnMap(&f, fieldIndex, goquTag.Values(), prefixes))
    34  			}
    35  		} else if f.PkgPath == "" {
    36  			dbTag := tag.New("db", f.Tag)
    37  			// if PkgPath is empty then it is an exported field
    38  			columnName := getColumnName(&f, dbTag)
    39  			if !shouldIgnoreField(dbTag) {
    40  				if !implementsScanner(f.Type) {
    41  					subCm := getStructColumnMap(&f, fieldIndex, []string{columnName}, prefixes)
    42  					if len(subCm) != 0 {
    43  						subColMaps = append(subColMaps, subCm)
    44  						continue
    45  					}
    46  				}
    47  				goquTag := tag.New("goqu", f.Tag)
    48  				columnName = strings.Join(append(prefixes, columnName), ".")
    49  				cm[columnName] = newColumnData(&f, columnName, fieldIndex, goquTag)
    50  			}
    51  		}
    52  	}
    53  	return cm.Merge(subColMaps)
    54  }
    55  
    56  func (cm ColumnMap) Cols() []string {
    57  	structCols := make([]string, 0, len(cm))
    58  	for key := range cm {
    59  		structCols = append(structCols, key)
    60  	}
    61  	sort.Strings(structCols)
    62  	return structCols
    63  }
    64  
    65  func (cm ColumnMap) Merge(colMaps []ColumnMap) ColumnMap {
    66  	for _, subCm := range colMaps {
    67  		for key, val := range subCm {
    68  			if _, ok := cm[key]; !ok {
    69  				cm[key] = val
    70  			}
    71  		}
    72  	}
    73  	return cm
    74  }
    75  
    76  func implementsScanner(t reflect.Type) bool {
    77  	if IsPointer(t.Kind()) {
    78  		t = t.Elem()
    79  	}
    80  	if reflect.PtrTo(t).Implements(scannerType) {
    81  		return true
    82  	}
    83  	if !IsStruct(t.Kind()) {
    84  		return true
    85  	}
    86  
    87  	return false
    88  }
    89  
    90  func newColumnData(f *reflect.StructField, columnName string, fieldIndex []int, goquTag tag.Options) ColumnData {
    91  	return ColumnData{
    92  		ColumnName:     columnName,
    93  		ShouldInsert:   !goquTag.Contains(skipInsertTagName),
    94  		ShouldUpdate:   !goquTag.Contains(skipUpdateTagName),
    95  		DefaultIfEmpty: goquTag.Contains(defaultIfEmptyTagName),
    96  		OmitNil:        goquTag.Contains(omitNilTagName),
    97  		OmitEmpty:      goquTag.Contains(omitEmptyTagName),
    98  		FieldIndex:     concatFieldIndexes(fieldIndex, f.Index),
    99  		GoType:         f.Type,
   100  	}
   101  }
   102  
   103  func getStructColumnMap(f *reflect.StructField, fieldIndex []int, fieldNames, prefixes []string) ColumnMap {
   104  	subFieldIndexes := concatFieldIndexes(fieldIndex, f.Index)
   105  	subPrefixes := append(prefixes, fieldNames...)
   106  	if f.Type.Kind() == reflect.Ptr {
   107  		return newColumnMap(f.Type.Elem(), subFieldIndexes, subPrefixes)
   108  	}
   109  	return newColumnMap(f.Type, subFieldIndexes, subPrefixes)
   110  }
   111  
   112  func getColumnName(f *reflect.StructField, dbTag tag.Options) string {
   113  	if dbTag.IsEmpty() {
   114  		return columnRenameFunction(f.Name)
   115  	}
   116  	return dbTag.Values()[0]
   117  }
   118  
   119  func shouldIgnoreField(dbTag tag.Options) bool {
   120  	if dbTag.Equals("-") {
   121  		return true
   122  	} else if dbTag.IsEmpty() && ignoreUntaggedFields {
   123  		return true
   124  	}
   125  
   126  	return false
   127  }
   128  
   129  // safely concat two fieldIndex slices into one.
   130  func concatFieldIndexes(fieldIndexPath, fieldIndex []int) []int {
   131  	fieldIndexes := make([]int, 0, len(fieldIndexPath)+len(fieldIndex))
   132  	fieldIndexes = append(fieldIndexes, fieldIndexPath...)
   133  	return append(fieldIndexes, fieldIndex...)
   134  }