github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/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) error {
    37  	settings := scanStructSettings{
    38  		TagName:                       "sql",
    39  		AllowMissingColumnsFromSelect: false,
    40  		AllowMissingFieldsInStruct:    false,
    41  	}
    42  	for _, opt := range opts {
    43  		opt.applyScanStructOption(&settings)
    44  	}
    45  	ptr := reflect.ValueOf(dst)
    46  	if ptr.Kind() != reflect.Pointer {
    47  		return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointer, ptr.Kind().String()))
    48  	}
    49  	if ptr.Elem().Kind() != reflect.Struct {
    50  		return xerrors.WithStackTrace(fmt.Errorf("%w: '%s'", errDstTypeIsNotAPointerToStruct, ptr.Elem().Kind().String()))
    51  	}
    52  	tt := ptr.Elem().Type()
    53  	missingColumns := make([]string, 0, len(s.data.columns))
    54  	existingFields := make(map[string]struct{}, tt.NumField())
    55  	for i := 0; i < tt.NumField(); i++ {
    56  		name := fieldName(tt.Field(i), settings.TagName)
    57  		v, err := s.data.seekByName(name)
    58  		if err != nil {
    59  			missingColumns = append(missingColumns, name)
    60  		} else {
    61  			if err = value.CastTo(v, ptr.Elem().Field(i).Addr().Interface()); err != nil {
    62  				return xerrors.WithStackTrace(err)
    63  			}
    64  			existingFields[name] = struct{}{}
    65  		}
    66  	}
    67  
    68  	if !settings.AllowMissingColumnsFromSelect && len(missingColumns) > 0 {
    69  		return xerrors.WithStackTrace(
    70  			fmt.Errorf("%w: '%v'", errColumnsNotFoundInRow, strings.Join(missingColumns, "','")),
    71  		)
    72  	}
    73  
    74  	if !settings.AllowMissingFieldsInStruct {
    75  		missingFields := make([]string, 0, tt.NumField())
    76  		for _, c := range s.data.columns {
    77  			if _, has := existingFields[c.GetName()]; !has {
    78  				missingFields = append(missingFields, c.GetName())
    79  			}
    80  		}
    81  		if len(missingFields) > 0 {
    82  			return xerrors.WithStackTrace(
    83  				fmt.Errorf("%w: '%v'", errFieldsNotFoundInStruct, strings.Join(missingFields, "','")),
    84  			)
    85  		}
    86  	}
    87  
    88  	return nil
    89  }