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 }