github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/query/scanner/struct.go (about) 1 package scanner 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" 9 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 10 ) 11 12 type scanStructSettings struct { 13 TagName string 14 AllowMissingColumnsFromSelect bool 15 AllowMissingFieldsInStruct bool 16 } 17 18 type StructScanner struct { 19 data *data 20 } 21 22 func Struct(data *data) StructScanner { 23 return StructScanner{ 24 data: data, 25 } 26 } 27 28 func fieldName(f reflect.StructField, tagName string) string { //nolint:gocritic 29 if name, has := f.Tag.Lookup(tagName); has { 30 return name 31 } 32 33 return f.Name 34 } 35 36 func (s StructScanner) ScanStruct(dst interface{}, opts ...ScanStructOption) (err error) { 37 settings := scanStructSettings{ 38 TagName: "sql", 39 AllowMissingColumnsFromSelect: false, 40 AllowMissingFieldsInStruct: false, 41 } 42 for _, opt := range opts { 43 if opt != nil { 44 opt.applyScanStructOption(&settings) 45 } 46 } 47 ptr := reflect.ValueOf(dst) 48 if ptr.Kind() != reflect.Pointer { 49 return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointer, ptr.Kind().String())) 50 } 51 if ptr.Elem().Kind() != reflect.Struct { 52 return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointerToStruct, ptr.Elem().Kind().String())) 53 } 54 tt := ptr.Elem().Type() 55 missingColumns := make([]string, 0, len(s.data.columns)) 56 existingFields := make(map[string]struct{}, tt.NumField()) 57 for i := 0; i < tt.NumField(); i++ { 58 name := fieldName(tt.Field(i), settings.TagName) 59 if name == "-" { 60 continue 61 } 62 63 v, err := s.data.seekByName(name) 64 if err != nil { 65 missingColumns = append(missingColumns, name) 66 } else { 67 if err = value.CastTo(v, ptr.Elem().Field(i).Addr().Interface()); err != nil { 68 return xerrors.WithStackTrace(fmt.Errorf("scan error on struct field name '%s': %w", name, err)) 69 } 70 existingFields[name] = struct{}{} 71 } 72 } 73 74 if !settings.AllowMissingColumnsFromSelect && len(missingColumns) > 0 { 75 return xerrors.WithStackTrace( 76 fmt.Errorf("%w: '%v'", ErrColumnsNotFoundInRow, strings.Join(missingColumns, "','")), 77 ) 78 } 79 80 if !settings.AllowMissingFieldsInStruct { 81 missingFields := make([]string, 0, tt.NumField()) 82 for _, c := range s.data.columns { 83 if _, has := existingFields[c.GetName()]; !has { 84 missingFields = append(missingFields, c.GetName()) 85 } 86 } 87 if len(missingFields) > 0 { 88 return xerrors.WithStackTrace( 89 fmt.Errorf("%w: '%v'", ErrFieldsNotFoundInStruct, strings.Join(missingFields, "','")), 90 ) 91 } 92 } 93 94 return nil 95 }