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