github.com/blend/go-sdk@v1.20220411.3/db/populate.go (about)

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