diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-09-07 11:51:17 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-09-07 11:51:17 +0200 |
| commit | 999a9b714c86c9845c20746dfa9abd746b710342 (patch) | |
| tree | 53edb6acaa8ee9e0e6c7b007a55485a1b0e5a413 /plan.go | |
| parent | 50c91a360ac3f42682d998a826087c4bb41d0af4 (diff) | |
| download | go-oblast-999a9b714c86c9845c20746dfa9abd746b710342.tar.gz | |
fix recursion into non-embedded struct fields
Diffstat (limited to 'plan.go')
| -rw-r--r-- | plan.go | 32 |
1 files changed, 26 insertions, 6 deletions
@@ -148,7 +148,7 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { } // discover addressable fields in this type, collect information from markers and tags - for _, field := range reflect.VisibleFields(t) { + for _, field := range assignableFields(t) { // recurse into struct fields (i.e. ignore the struct itself and consider its members instead) // unless the field itself has a `db:"..."` tag if field.Type.Kind() == reflect.Struct || (field.Type.Kind() == reflect.Pointer && field.Type.Elem().Kind() == reflect.Struct) { @@ -168,11 +168,6 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { indexesOfOpaqueStructs = append(indexesOfOpaqueStructs, field.Index) } - // ignore unexported fields (otherwise reflect.Value.Interface() on the field would panic) - if field.PkgPath != "" { - continue - } - // ignore fields that are within a struct type that is mapped as a whole if slices.ContainsFunc(indexesOfOpaqueStructs, func(index []int) bool { return isWithin(field.Index, index) @@ -288,6 +283,31 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { return p, nil } +// Like reflect.VisibleFields(), but considers all fields within the type that +// are assignable (i.e. `v.FieldByIndex(...).Set(...)` does not panic). +func assignableFields(t reflect.Type) (result []reflect.StructField) { + for field := range t.Fields() { + // assignment is allowed for exported or embedded fields only + if field.IsExported() || field.Anonymous { + result = append(result, field) + + // recurse into struct fields + ft := field.Type + if ft.Kind() == reflect.Pointer { + ft = ft.Elem() + } + if ft.Kind() == reflect.Struct { + for _, subfield := range assignableFields(ft) { + subfield.Index = append(slices.Clone(field.Index), subfield.Index...) + result = append(result, subfield) + } + } + } + } + + return result +} + func (p plan) getNonAutoColumnNames() []string { result := make([]string, 0, len(p.AllColumnNames)-len(p.AutoColumnNames)) for _, columnName := range p.AllColumnNames { |
