311 lines
9.1 KiB
Go
311 lines
9.1 KiB
Go
package dbr
|
|
|
|
import (
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
// Unvetted thots:
|
|
// Given a query and given a structure (field list), there's 2 sets of fields.
|
|
// Take the intersection. We can fill those in. great.
|
|
// For fields in the structure that aren't in the query, we'll let that slide if db:"-"
|
|
// For fields in the structure that aren't in the query but without db:"-", return error
|
|
// For fields in the query that aren't in the structure, we'll ignore them.
|
|
|
|
// LoadStructs executes the SelectBuilder and loads the resulting data into a slice of structs
|
|
// dest must be a pointer to a slice of pointers to structs
|
|
// Returns the number of items found (which is not necessarily the # of items set)
|
|
func (b *SelectBuilder) LoadStructs(dest interface{}) (int, error) {
|
|
//
|
|
// Validate the dest, and extract the reflection values we need.
|
|
//
|
|
|
|
// This must be a pointer to a slice
|
|
valueOfDest := reflect.ValueOf(dest)
|
|
kindOfDest := valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Ptr {
|
|
panic("invalid type passed to LoadStructs. Need a pointer to a slice")
|
|
}
|
|
|
|
// This must a slice
|
|
valueOfDest = reflect.Indirect(valueOfDest)
|
|
kindOfDest = valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Slice {
|
|
panic("invalid type passed to LoadStructs. Need a pointer to a slice")
|
|
}
|
|
|
|
// The slice elements must be pointers to structures
|
|
recordType := valueOfDest.Type().Elem()
|
|
if recordType.Kind() != reflect.Ptr {
|
|
panic("Elements need to be pointers to structures")
|
|
}
|
|
|
|
recordType = recordType.Elem()
|
|
if recordType.Kind() != reflect.Struct {
|
|
panic("Elements need to be pointers to structures")
|
|
}
|
|
|
|
//
|
|
// Get full SQL
|
|
//
|
|
fullSql, err := Interpolate(b.ToSql())
|
|
if err != nil {
|
|
return 0, b.EventErr("dbr.select.load_all.interpolate", err)
|
|
}
|
|
|
|
numberOfRowsReturned := 0
|
|
|
|
// Start the timer:
|
|
startTime := time.Now()
|
|
defer func() { b.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{"sql": fullSql}) }()
|
|
|
|
// Run the query:
|
|
rows, err := b.Runner.Query(fullSql)
|
|
if err != nil {
|
|
return 0, b.EventErrKv("dbr.select.load_all.query", err, kvs{"sql": fullSql})
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Get the columns returned
|
|
columns, err := rows.Columns()
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_one.rows.Columns", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Create a map of this result set to the struct fields
|
|
fieldMap, err := b.calculateFieldMap(recordType, columns, false)
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all.calculateFieldMap", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Build a 'holder', which is an []interface{}. Each value will be the set to address of the field corresponding to our newly made records:
|
|
holder := make([]interface{}, len(fieldMap))
|
|
|
|
// Iterate over rows and scan their data into the structs
|
|
sliceValue := valueOfDest
|
|
for rows.Next() {
|
|
// Create a new record to store our row:
|
|
pointerToNewRecord := reflect.New(recordType)
|
|
newRecord := reflect.Indirect(pointerToNewRecord)
|
|
|
|
// Prepare the holder for this record
|
|
scannable, err := b.prepareHolderFor(newRecord, fieldMap, holder)
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all.holderFor", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Load up our new structure with the row's values
|
|
err = rows.Scan(scannable...)
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all.scan", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Append our new record to the slice:
|
|
sliceValue = reflect.Append(sliceValue, pointerToNewRecord)
|
|
|
|
numberOfRowsReturned++
|
|
}
|
|
valueOfDest.Set(sliceValue)
|
|
|
|
// Check for errors at the end. Supposedly these are error that can happen during iteration.
|
|
if err = rows.Err(); err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all.rows_err", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
return numberOfRowsReturned, nil
|
|
}
|
|
|
|
// LoadStruct executes the SelectBuilder and loads the resulting data into a struct
|
|
// dest must be a pointer to a struct
|
|
// Returns ErrNotFound if nothing was found
|
|
func (b *SelectBuilder) LoadStruct(dest interface{}) error {
|
|
//
|
|
// Validate the dest, and extract the reflection values we need.
|
|
//
|
|
valueOfDest := reflect.ValueOf(dest)
|
|
indirectOfDest := reflect.Indirect(valueOfDest)
|
|
kindOfDest := valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Ptr || indirectOfDest.Kind() != reflect.Struct {
|
|
panic("you need to pass in the address of a struct")
|
|
}
|
|
|
|
recordType := indirectOfDest.Type()
|
|
|
|
//
|
|
// Get full SQL
|
|
//
|
|
fullSql, err := Interpolate(b.ToSql())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start the timer:
|
|
startTime := time.Now()
|
|
defer func() { b.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{"sql": fullSql}) }()
|
|
|
|
// Run the query:
|
|
rows, err := b.Runner.Query(fullSql)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.query", err, kvs{"sql": fullSql})
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Get the columns of this result set
|
|
columns, err := rows.Columns()
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.rows.Columns", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Create a map of this result set to the struct columns
|
|
fieldMap, err := b.calculateFieldMap(recordType, columns, false)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.calculateFieldMap", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Build a 'holder', which is an []interface{}. Each value will be the set to address of the field corresponding to our newly made records:
|
|
holder := make([]interface{}, len(fieldMap))
|
|
|
|
if rows.Next() {
|
|
// Build a 'holder', which is an []interface{}. Each value will be the address of the field corresponding to our newly made record:
|
|
scannable, err := b.prepareHolderFor(indirectOfDest, fieldMap, holder)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.holderFor", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Load up our new structure with the row's values
|
|
err = rows.Scan(scannable...)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.scan", err, kvs{"sql": fullSql})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return b.EventErrKv("dbr.select.load_one.rows_err", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
return ErrNotFound
|
|
}
|
|
|
|
// LoadValues executes the SelectBuilder and loads the resulting data into a slice of primitive values
|
|
// Returns ErrNotFound if no value was found, and it was therefore not set.
|
|
func (b *SelectBuilder) LoadValues(dest interface{}) (int, error) {
|
|
// Validate the dest and reflection values we need
|
|
|
|
// This must be a pointer to a slice
|
|
valueOfDest := reflect.ValueOf(dest)
|
|
kindOfDest := valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Ptr {
|
|
panic("invalid type passed to LoadValues. Need a pointer to a slice")
|
|
}
|
|
|
|
// This must a slice
|
|
valueOfDest = reflect.Indirect(valueOfDest)
|
|
kindOfDest = valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Slice {
|
|
panic("invalid type passed to LoadValues. Need a pointer to a slice")
|
|
}
|
|
|
|
recordType := valueOfDest.Type().Elem()
|
|
|
|
recordTypeIsPtr := recordType.Kind() == reflect.Ptr
|
|
if recordTypeIsPtr {
|
|
reflect.ValueOf(dest)
|
|
}
|
|
|
|
//
|
|
// Get full SQL
|
|
//
|
|
fullSql, err := Interpolate(b.ToSql())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
numberOfRowsReturned := 0
|
|
|
|
// Start the timer:
|
|
startTime := time.Now()
|
|
defer func() { b.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{"sql": fullSql}) }()
|
|
|
|
// Run the query:
|
|
rows, err := b.Runner.Query(fullSql)
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all_values.query", err, kvs{"sql": fullSql})
|
|
}
|
|
defer rows.Close()
|
|
|
|
sliceValue := valueOfDest
|
|
for rows.Next() {
|
|
// Create a new value to store our row:
|
|
pointerToNewValue := reflect.New(recordType)
|
|
newValue := reflect.Indirect(pointerToNewValue)
|
|
|
|
err = rows.Scan(pointerToNewValue.Interface())
|
|
if err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all_values.scan", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
// Append our new value to the slice:
|
|
sliceValue = reflect.Append(sliceValue, newValue)
|
|
|
|
numberOfRowsReturned++
|
|
}
|
|
valueOfDest.Set(sliceValue)
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return numberOfRowsReturned, b.EventErrKv("dbr.select.load_all_values.rows_err", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
return numberOfRowsReturned, nil
|
|
}
|
|
|
|
// LoadValue executes the SelectBuilder and loads the resulting data into a primitive value
|
|
// Returns ErrNotFound if no value was found, and it was therefore not set.
|
|
func (b *SelectBuilder) LoadValue(dest interface{}) error {
|
|
// Validate the dest
|
|
valueOfDest := reflect.ValueOf(dest)
|
|
kindOfDest := valueOfDest.Kind()
|
|
|
|
if kindOfDest != reflect.Ptr {
|
|
panic("Destination must be a pointer")
|
|
}
|
|
|
|
//
|
|
// Get full SQL
|
|
//
|
|
fullSql, err := Interpolate(b.ToSql())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start the timer:
|
|
startTime := time.Now()
|
|
defer func() { b.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{"sql": fullSql}) }()
|
|
|
|
// Run the query:
|
|
rows, err := b.Runner.Query(fullSql)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_value.query", err, kvs{"sql": fullSql})
|
|
}
|
|
defer rows.Close()
|
|
|
|
if rows.Next() {
|
|
err = rows.Scan(dest)
|
|
if err != nil {
|
|
return b.EventErrKv("dbr.select.load_value.scan", err, kvs{"sql": fullSql})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return b.EventErrKv("dbr.select.load_value.rows_err", err, kvs{"sql": fullSql})
|
|
}
|
|
|
|
return ErrNotFound
|
|
}
|