• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

huandu / go-sqlbuilder / 23833412834

01 Apr 2026 05:24AM UTC coverage: 96.575% (-0.06%) from 96.639%
23833412834

push

github

huandu
fix #234: field names in INSERT INTO should not include table name

94 of 97 new or added lines in 2 files covered. (96.91%)

1 existing line in 1 file now uncovered.

3948 of 4088 relevant lines covered (96.58%)

2.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.72
/structfields.go
1
package sqlbuilder
2

3
import (
4
        "database/sql"
5
        "fmt"
6
        "reflect"
7
        "strings"
8
        "sync"
9
)
10

11
var typeOfSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
12

13
type structFields struct {
14
        noTag  *structTaggedFields
15
        tagged map[string]*structTaggedFields
16
}
17

18
type structTaggedFields struct {
19
        // All columns for SELECT.
20
        ForRead     []*structField
21
        colsForRead map[string]*structField
22

23
        // All columns which can be used in INSERT and UPDATE.
24
        ForWrite     []*structField
25
        colsForWrite map[string]struct{}
26

27
        // All columns which can be used in INSERT.
28
        ForInsert     []*structField
29
        colsForInsert map[string]struct{}
30
}
31

32
type structField struct {
33
        Name     string
34
        Alias    string
35
        As       string
36
        Tags     []string
37
        IsQuoted bool
38
        DBTag    string
39
        Field    reflect.StructField
40
        Index    []int
41

42
        omitEmptyTags omitEmptyTagMap
43
}
44

45
type structFieldsParser func() *structFields
46

47
func makeDefaultFieldsParser(t reflect.Type) structFieldsParser {
2✔
48
        return makeFieldsParser(t, nil, true)
2✔
49
}
2✔
50

51
func makeCustomFieldsParser(t reflect.Type, mapper FieldMapperFunc) structFieldsParser {
2✔
52
        return makeFieldsParser(t, mapper, false)
2✔
53
}
2✔
54

55
func makeFieldsParser(t reflect.Type, mapper FieldMapperFunc, useDefault bool) structFieldsParser {
2✔
56
        var once sync.Once
2✔
57
        sfs := &structFields{
2✔
58
                noTag:  makeStructTaggedFields(),
2✔
59
                tagged: map[string]*structTaggedFields{},
2✔
60
        }
2✔
61

2✔
62
        return func() *structFields {
4✔
63
                once.Do(func() {
4✔
64
                        if useDefault {
4✔
65
                                mapper = DefaultFieldMapper
2✔
66
                        }
2✔
67

68
                        sfs.parse(t, mapper, "", nil, true)
2✔
69
                })
70

71
                return sfs
2✔
72
        }
73
}
74

75
func (sfs *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix string, index []int, allowInsert bool) {
2✔
76
        l := t.NumField()
2✔
77
        var anonymous []reflect.StructField
2✔
78

2✔
79
        for i := 0; i < l; i++ {
4✔
80
                field := t.Field(i)
2✔
81

2✔
82
                // Skip unexported fields that are not embedded structs.
2✔
83
                if field.PkgPath != "" && !field.Anonymous {
4✔
84
                        continue
2✔
85
                }
86

87
                if field.Anonymous {
4✔
88
                        ft := field.Type
2✔
89

2✔
90
                        // If field is an anonymous struct or pointer to struct, parse it later.
2✔
91
                        if shouldExpandAnonymousStructField(ft) {
4✔
92
                                anonymous = append(anonymous, field)
2✔
93
                                continue
2✔
94
                        }
95
                }
96

97
                // Parse DBTag.
98
                alias, dbtag := DefaultGetAlias(&field)
2✔
99

2✔
100
                if alias == "-" {
4✔
101
                        continue
2✔
102
                }
103

104
                if shouldExpandTaggedStructField(field.Type, dbtag) {
4✔
105
                        structField := makeStructField(field, alias, dbtag, mapper, prefix, index, i)
2✔
106
                        if allowInsert {
4✔
107
                                sfs.addInsertField(structField)
2✔
108
                        }
2✔
109
                        sfs.parse(dereferencedType(field.Type), mapper, dbtag+".", appendFieldIndex(index, i), false)
2✔
110
                        continue
2✔
111
                }
112

113
                structField := makeStructField(field, alias, dbtag, mapper, prefix, index, i)
2✔
114
                if allowInsert {
4✔
115
                        sfs.addField(structField)
2✔
116
                } else {
4✔
117
                        sfs.addReadWriteField(structField)
2✔
118
                }
2✔
119
        }
120

121
        for _, field := range anonymous {
4✔
122
                ft := dereferencedType(field.Type)
2✔
123
                sfs.parse(ft, mapper, prefix, appendFieldIndex(index, field.Index...), allowInsert)
2✔
124
        }
2✔
125
}
126

127
func makeStructField(field reflect.StructField, alias, dbtag string, mapper FieldMapperFunc, prefix string, index []int, fieldIndex int) *structField {
2✔
128
        if alias == "" {
4✔
129
                alias = field.Name
2✔
130
                if mapper != nil {
4✔
131
                        alias = mapper(alias)
2✔
132
                }
2✔
133
        }
134

135
        if prefix != "" && !strings.ContainsRune(alias, '.') {
4✔
136
                alias = prefix + alias
2✔
137
        }
2✔
138

139
        fieldopt := field.Tag.Get(FieldOpt)
2✔
140
        opts := optRegex.FindAllString(fieldopt, -1)
2✔
141
        isQuoted := false
2✔
142
        omitEmptyTags := omitEmptyTagMap{}
2✔
143

2✔
144
        for _, opt := range opts {
4✔
145
                optMap := getOptMatchedMap(opt)
2✔
146

2✔
147
                switch optMap[optName] {
2✔
148
                case fieldOptOmitEmpty:
2✔
149
                        tags := getTagsFromOptParams(optMap[optParams])
2✔
150

2✔
151
                        for _, tag := range tags {
4✔
152
                                omitEmptyTags[tag] = struct{}{}
2✔
153
                        }
2✔
154

155
                case fieldOptWithQuote:
2✔
156
                        isQuoted = true
2✔
157
                }
158
        }
159

160
        fieldas := field.Tag.Get(FieldAs)
2✔
161
        fieldtag := field.Tag.Get(FieldTag)
2✔
162
        tags := splitTags(fieldtag)
2✔
163

2✔
164
        return &structField{
2✔
165
                Name:          field.Name,
2✔
166
                Alias:         alias,
2✔
167
                As:            fieldas,
2✔
168
                Tags:          tags,
2✔
169
                IsQuoted:      isQuoted,
2✔
170
                DBTag:         dbtag,
2✔
171
                Field:         field,
2✔
172
                Index:         appendFieldIndex(index, fieldIndex),
2✔
173
                omitEmptyTags: omitEmptyTags,
2✔
174
        }
2✔
175
}
176

177
func (sfs *structFields) addField(field *structField) {
2✔
178
        sfs.addReadWriteField(field)
2✔
179
        sfs.addInsertField(field)
2✔
180
}
2✔
181

182
func (sfs *structFields) addReadWriteField(field *structField) {
2✔
183
        sfs.noTag.AddReadWrite(field)
2✔
184

2✔
185
        for _, tag := range field.Tags {
4✔
186
                sfs.taggedFields(tag).AddReadWrite(field)
2✔
187
        }
2✔
188
}
189

190
func (sfs *structFields) addInsertField(field *structField) {
2✔
191
        sfs.noTag.AddInsert(field)
2✔
192

2✔
193
        for _, tag := range field.Tags {
4✔
194
                sfs.taggedFields(tag).AddInsert(field)
2✔
195
        }
2✔
196
}
197

198
func appendFieldIndex(prefix []int, index ...int) []int {
2✔
199
        path := make([]int, 0, len(prefix)+len(index))
2✔
200
        path = append(path, prefix...)
2✔
201
        path = append(path, index...)
2✔
202
        return path
2✔
203
}
2✔
204

205
func shouldExpandAnonymousStructField(t reflect.Type) bool {
2✔
206
        return canExpandStructType(t)
2✔
207
}
2✔
208

209
func shouldExpandTaggedStructField(t reflect.Type, dbtag string) bool {
2✔
210
        return dbtag != "" && canExpandStructType(t)
2✔
211
}
2✔
212

213
func canExpandStructType(t reflect.Type) bool {
2✔
214
        if t == nil {
2✔
215
                return false
×
216
        }
×
217

218
        dt := dereferencedType(t)
2✔
219
        if dt.Kind() != reflect.Struct {
4✔
220
                return false
2✔
221
        }
2✔
222

223
        if implementsScannerOrValuer(t) || implementsScannerOrValuer(dt) {
2✔
224
                return false
×
225
        }
×
226

227
        if dt != t && implementsScannerOrValuer(reflect.PtrTo(dt)) {
2✔
228
                return false
×
229
        }
×
230

231
        for i := 0; i < dt.NumField(); i++ {
4✔
232
                field := dt.Field(i)
2✔
233
                if field.PkgPath == "" || field.Anonymous {
4✔
234
                        return true
2✔
235
                }
2✔
236
        }
237

238
        return false
2✔
239
}
240

241
func implementsScannerOrValuer(t reflect.Type) bool {
2✔
242
        if t == nil {
2✔
243
                return false
×
244
        }
×
245

246
        return t.Implements(typeOfSQLDriverValuer) || t.Implements(typeOfSQLScanner)
2✔
247
}
248

249
func (sfs *structFields) FilterTags(with, without []string) *structTaggedFields {
2✔
250
        if len(with) == 0 && len(without) == 0 {
4✔
251
                return sfs.noTag
2✔
252
        }
2✔
253

254
        // Simply return the tagged fields.
255
        if len(with) == 1 && len(without) == 0 {
4✔
256
                return sfs.tagged[with[0]]
2✔
257
        }
2✔
258

259
        // Find out all with and without fields.
260
        taggedFields := makeStructTaggedFields()
2✔
261
        filteredReadFields := make(map[string]struct{}, len(sfs.noTag.colsForRead))
2✔
262
        filteredInsertFields := make(map[string]struct{}, len(sfs.noTag.colsForInsert))
2✔
263

2✔
264
        for _, tag := range without {
4✔
265
                if field, ok := sfs.tagged[tag]; ok {
4✔
266
                        for k := range field.colsForRead {
4✔
267
                                filteredReadFields[k] = struct{}{}
2✔
268
                        }
2✔
269

270
                        for k := range field.colsForInsert {
4✔
271
                                filteredInsertFields[k] = struct{}{}
2✔
272
                        }
2✔
273
                }
274
        }
275

276
        if len(with) == 0 {
4✔
277
                for _, field := range sfs.noTag.ForRead {
4✔
278
                        k := field.Key()
2✔
279

2✔
280
                        if _, ok := filteredReadFields[k]; !ok {
4✔
281
                                taggedFields.AddReadWrite(field)
2✔
282
                        }
2✔
283
                }
284

285
                for _, field := range sfs.noTag.ForInsert {
4✔
286
                        if _, ok := filteredInsertFields[field.Alias]; !ok {
4✔
287
                                taggedFields.AddInsert(field)
2✔
288
                        }
2✔
289
                }
290
        } else {
2✔
291
                for _, tag := range with {
4✔
292
                        if fields, ok := sfs.tagged[tag]; ok {
4✔
293
                                for _, field := range fields.ForRead {
4✔
294
                                        k := field.Key()
2✔
295

2✔
296
                                        if _, ok := filteredReadFields[k]; !ok {
4✔
297
                                                taggedFields.AddReadWrite(field)
2✔
298
                                        }
2✔
299
                                }
300

301
                                for _, field := range fields.ForInsert {
4✔
302
                                        if _, ok := filteredInsertFields[field.Alias]; !ok {
4✔
303
                                                taggedFields.AddInsert(field)
2✔
304
                                        }
2✔
305
                                }
306
                        }
307
                }
308
        }
309

310
        return taggedFields
2✔
311
}
312

313
func (sfs *structFields) taggedFields(tag string) *structTaggedFields {
2✔
314
        fields, ok := sfs.tagged[tag]
2✔
315

2✔
316
        if !ok {
4✔
317
                fields = makeStructTaggedFields()
2✔
318
                sfs.tagged[tag] = fields
2✔
319
        }
2✔
320

321
        return fields
2✔
322
}
323

324
func makeStructTaggedFields() *structTaggedFields {
2✔
325
        return &structTaggedFields{
2✔
326
                colsForRead:   map[string]*structField{},
2✔
327
                colsForWrite:  map[string]struct{}{},
2✔
328
                colsForInsert: map[string]struct{}{},
2✔
329
        }
2✔
330
}
2✔
331

332
// Add a new field to stfs.
333
// If field's key exists in stfs.fields, the field is ignored.
UNCOV
334
func (stfs *structTaggedFields) Add(field *structField) {
×
NEW
335
        stfs.AddReadWrite(field)
×
NEW
336
        stfs.AddInsert(field)
×
NEW
337
}
×
338

339
func (stfs *structTaggedFields) AddReadWrite(field *structField) {
2✔
340
        key := field.Key()
2✔
341

2✔
342
        if _, ok := stfs.colsForRead[key]; !ok {
4✔
343
                stfs.colsForRead[key] = field
2✔
344
                stfs.ForRead = append(stfs.ForRead, field)
2✔
345
        }
2✔
346

347
        key = field.Alias
2✔
348

2✔
349
        if _, ok := stfs.colsForWrite[key]; !ok {
4✔
350
                stfs.colsForWrite[key] = struct{}{}
2✔
351
                stfs.ForWrite = append(stfs.ForWrite, field)
2✔
352
        }
2✔
353
}
354

355
func (stfs *structTaggedFields) AddInsert(field *structField) {
2✔
356
        key := field.Alias
2✔
357

2✔
358
        if _, ok := stfs.colsForInsert[key]; !ok {
4✔
359
                stfs.colsForInsert[key] = struct{}{}
2✔
360
                stfs.ForInsert = append(stfs.ForInsert, field)
2✔
361
        }
2✔
362
}
363

364
// Cols returns the fields whose key is one of cols.
365
// If any column in cols doesn't exist in sfs.fields, returns nil.
366
func (stfs *structTaggedFields) Cols(cols []string) []*structField {
2✔
367
        fields := make([]*structField, 0, len(cols))
2✔
368

2✔
369
        for _, col := range cols {
4✔
370
                field := stfs.colsForRead[col]
2✔
371

2✔
372
                if field == nil {
4✔
373
                        return nil
2✔
374
                }
2✔
375

376
                fields = append(fields, field)
2✔
377
        }
378

379
        return fields
2✔
380
}
381

382
// Key returns the key name to identify a field.
383
func (sf *structField) Key() string {
2✔
384
        if sf.As != "" {
4✔
385
                return sf.As
2✔
386
        }
2✔
387

388
        if sf.Alias != "" {
4✔
389
                return sf.Alias
2✔
390
        }
2✔
391

392
        return sf.Name
×
393
}
394

395
// NameForSelect returns the name for SELECT.
396
func (sf *structField) NameForSelect(flavor Flavor) string {
2✔
397
        if sf.As == "" {
4✔
398
                return sf.Quote(flavor)
2✔
399
        }
2✔
400

401
        return fmt.Sprintf("%s AS %s", sf.Quote(flavor), sf.As)
2✔
402
}
403

404
// Quote the Alias in sf with flavor.
405
func (sf *structField) Quote(flavor Flavor) string {
2✔
406
        if !sf.IsQuoted {
4✔
407
                return sf.Alias
2✔
408
        }
2✔
409

410
        return flavor.Quote(sf.Alias)
2✔
411
}
412

413
// ShouldOmitEmpty returns true only if any one of tags is in the omitted tags map.
414
func (sf *structField) ShouldOmitEmpty(tags ...string) (ret bool) {
2✔
415
        omit := sf.omitEmptyTags
2✔
416

2✔
417
        if len(omit) == 0 {
4✔
418
                return
2✔
419
        }
2✔
420

421
        // Always check default tag.
422
        if _, ret = omit[""]; ret {
4✔
423
                return
2✔
424
        }
2✔
425

426
        for _, tag := range tags {
4✔
427
                if _, ret = omit[tag]; ret {
4✔
428
                        return
2✔
429
                }
2✔
430
        }
431

432
        return
2✔
433
}
434

435
type omitEmptyTagMap map[string]struct{}
436

437
func getOptMatchedMap(opt string) (res map[string]string) {
2✔
438
        res = map[string]string{}
2✔
439
        sm := optRegex.FindStringSubmatch(opt)
2✔
440

2✔
441
        for i, name := range optRegex.SubexpNames() {
4✔
442
                if name != "" {
4✔
443
                        res[name] = sm[i]
2✔
444
                }
2✔
445
        }
446

447
        return
2✔
448
}
449

450
func getTagsFromOptParams(opts string) (tags []string) {
2✔
451
        tags = splitTags(opts)
2✔
452

2✔
453
        if len(tags) == 0 {
4✔
454
                tags = append(tags, "")
2✔
455
        }
2✔
456

457
        return
2✔
458
}
459

460
func splitTags(fieldtag string) (tags []string) {
2✔
461
        parts := strings.Split(fieldtag, ",")
2✔
462

2✔
463
        for _, v := range parts {
4✔
464
                tag := strings.TrimSpace(v)
2✔
465

2✔
466
                if tag == "" {
4✔
467
                        continue
2✔
468
                }
469

470
                tags = append(tags, tag)
2✔
471
        }
472

473
        return
2✔
474
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc