go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/populate.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package db 9 10 import ( 11 "database/sql" 12 "fmt" 13 "reflect" 14 ) 15 16 // PopulateByName sets the values of an object from the values of a sql.Rows object using column names. 17 func PopulateByName(object any, row Rows, cols *TypeMeta) error { 18 rowColumns, err := row.Columns() 19 if err != nil { 20 return err 21 } 22 23 var values = make([]interface{}, len(rowColumns)) 24 var columnLookup = cols.Lookup() 25 for i, name := range rowColumns { 26 if col, ok := columnLookup[name]; ok { 27 initColumnValue(i, values, col) 28 } else { 29 var value any 30 values[i] = &value 31 } 32 } 33 34 err = row.Scan(values...) 35 if err != nil { 36 return err 37 } 38 39 var colName string 40 var field *Column 41 var ok bool 42 43 objectValue := reflectValue(object) 44 for i, v := range values { 45 colName = rowColumns[i] 46 if field, ok = columnLookup[colName]; ok { 47 err = field.SetValueReflected(objectValue, v) 48 if err != nil { 49 return err 50 } 51 } 52 } 53 54 return nil 55 } 56 57 // IsPopulatable returns if an object is populatable 58 func IsPopulatable(object any) (isPopulatable bool) { 59 _, isPopulatable = object.(Populatable) 60 return 61 } 62 63 // IsScanner returns if an scalar value is scannable 64 func IsScanner(object any) (isScanner bool) { 65 _, isScanner = object.(sql.Scanner) 66 return 67 } 68 69 // PopulateInOrder sets the values of an object in order from a sql.Rows object. 70 // Only use this method if you're certain of the column order. It is faster than populateByName. 71 // Optionally if your object implements Populatable this process will be skipped completely, which is even faster. 72 func PopulateInOrder(object any, row Scanner, cols []*Column) (err error) { 73 var values = make([]interface{}, len(cols)) 74 75 for i, col := range cols { 76 initColumnValue(i, values, col) 77 } 78 if err = row.Scan(values...); err != nil { 79 return err 80 } 81 82 objectValue := reflectValue(object) 83 var field *Column 84 for i, v := range values { 85 field = cols[i] 86 if err = field.SetValueReflected(objectValue, v); err != nil { 87 return 88 } 89 } 90 91 return 92 } 93 94 // Zero resets an object. 95 func Zero(object any) error { 96 objectValue := reflect.ValueOf(object) 97 if !objectValue.Elem().CanSet() { 98 return fmt.Errorf("zero; cannot set object, did you pass a reference?") 99 } 100 objectValue.Elem().Set(reflect.Zero(objectValue.Type().Elem())) 101 return nil 102 } 103 104 // initColumnValue inserts the correct placeholder in the scan array of values. 105 // it will use `sql.Null` forms where appropriate. 106 // JSON fields are implicitly nullable. 107 func initColumnValue(index int, values []interface{}, col *Column) { 108 if col.IsJSON { 109 values[index] = &sql.NullString{} 110 } else if col.FieldType.Kind() == reflect.Ptr { 111 values[index] = reflect.New(col.FieldType).Interface() 112 } else { 113 values[index] = reflect.New(reflect.PtrTo(col.FieldType)).Interface() 114 } 115 }