jet/qrm/qrm.go

361 lines
8.9 KiB
Go
Raw Normal View History

package qrm
import (
2019-06-20 12:22:19 +02:00
"context"
"database/sql"
"errors"
"fmt"
"reflect"
"github.com/go-jet/jet/v2/internal/utils"
)
// ErrNoRows is returned by Query when query result set is empty
var ErrNoRows = errors.New("qrm: no rows in result set")
// Query executes Query Result Mapping (QRM) of `query` with list of parametrized arguments `arg` over database connection `db`
// using context `ctx` into destination `destPtr`.
// Destination can be either pointer to struct or pointer to slice of structs.
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
func Query(ctx context.Context, db DB, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) {
2019-05-20 17:37:55 +02:00
utils.MustBeInitializedPtr(db, "jet: db is nil")
utils.MustBeInitializedPtr(destPtr, "jet: destination is nil")
utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct")
2019-05-20 17:37:55 +02:00
destinationPtrType := reflect.TypeOf(destPtr)
2019-05-20 17:37:55 +02:00
if destinationPtrType.Elem().Kind() == reflect.Slice {
rowsProcessed, err := queryToSlice(ctx, db, query, args, destPtr)
if err != nil {
return rowsProcessed, fmt.Errorf("jet: %w", err)
}
return rowsProcessed, nil
2019-05-20 17:37:55 +02:00
} else if destinationPtrType.Elem().Kind() == reflect.Struct {
tempSlicePtrValue := reflect.New(reflect.SliceOf(destinationPtrType))
tempSliceValue := tempSlicePtrValue.Elem()
rowsProcessed, err := queryToSlice(ctx, db, query, args, tempSlicePtrValue.Interface())
2019-05-20 17:37:55 +02:00
if err != nil {
return rowsProcessed, fmt.Errorf("jet: %w", err)
2019-05-20 17:37:55 +02:00
}
if rowsProcessed == 0 {
return 0, ErrNoRows
2019-05-20 17:37:55 +02:00
}
// edge case when row result set contains only NULLs.
if tempSliceValue.Len() == 0 {
return rowsProcessed, nil
}
structValue := reflect.ValueOf(destPtr).Elem()
2019-05-20 17:37:55 +02:00
firstTempStruct := tempSliceValue.Index(0).Elem()
if structValue.Type().AssignableTo(firstTempStruct.Type()) {
structValue.Set(tempSliceValue.Index(0).Elem())
}
return rowsProcessed, nil
2019-05-20 17:37:55 +02:00
} else {
panic("jet: destination has to be a pointer to slice or pointer to struct")
2019-05-20 17:37:55 +02:00
}
}
2021-05-16 19:10:43 +02:00
// ScanOneRowToDest will scan one row into struct destination
func ScanOneRowToDest(rows *sql.Rows, destPtr interface{}) error {
utils.MustBeInitializedPtr(destPtr, "jet: destination is nil")
utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct")
scanContext, err := newScanContext(rows)
if err != nil {
return fmt.Errorf("failed to create scan context, %w", err)
}
if len(scanContext.row) == 0 {
return errors.New("empty row slice")
}
err = rows.Scan(scanContext.row...)
if err != nil {
return fmt.Errorf("rows scan error, %w", err)
}
destinationPtrType := reflect.TypeOf(destPtr)
tempSlicePtrValue := reflect.New(reflect.SliceOf(destinationPtrType))
tempSliceValue := tempSlicePtrValue.Elem()
_, err = mapRowToSlice(scanContext, "", newTypeStack(), tempSlicePtrValue, nil)
if err != nil {
return fmt.Errorf("failed to map a row, %w", err)
}
// edge case when row result set contains only NULLs.
if tempSliceValue.Len() == 0 {
return nil
}
destValue := reflect.ValueOf(destPtr).Elem()
firstTempSliceValue := tempSliceValue.Index(0).Elem()
if destValue.Type().AssignableTo(firstTempSliceValue.Type()) {
destValue.Set(tempSliceValue.Index(0).Elem())
}
return nil
}
func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, slicePtr interface{}) (rowsProcessed int64, err error) {
2019-06-20 12:22:19 +02:00
if ctx == nil {
ctx = context.Background()
}
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return
}
2019-03-09 14:20:44 +01:00
defer rows.Close()
2019-05-20 17:37:55 +02:00
scanContext, err := newScanContext(rows)
2019-03-14 09:18:23 +01:00
2019-05-20 17:37:55 +02:00
if err != nil {
return
2019-03-14 09:18:23 +01:00
}
2019-05-20 17:37:55 +02:00
if len(scanContext.row) == 0 {
return
2019-05-20 17:37:55 +02:00
}
slicePtrValue := reflect.ValueOf(slicePtr)
for rows.Next() {
err = rows.Scan(scanContext.row...)
if err != nil {
return scanContext.rowNum, err
}
2019-03-15 21:55:43 +01:00
scanContext.rowNum++
_, err = mapRowToSlice(scanContext, "", newTypeStack(), slicePtrValue, nil)
2019-05-20 17:37:55 +02:00
if err != nil {
return scanContext.rowNum, err
}
}
err = rows.Close()
if err != nil {
return scanContext.rowNum, err
}
return scanContext.rowNum, rows.Err()
}
func mapRowToSlice(
scanContext *scanContext,
groupKey string,
typesVisited *typeStack,
slicePtrValue reflect.Value,
field *reflect.StructField) (updated bool, err error) {
2019-04-04 13:07:21 +02:00
2019-05-20 17:37:55 +02:00
sliceElemType := getSliceElemType(slicePtrValue)
2019-03-14 09:18:23 +01:00
2019-07-29 18:08:53 +02:00
if isSimpleModelType(sliceElemType) {
2019-07-13 13:17:28 +02:00
updated, err = mapRowToBaseTypeSlice(scanContext, slicePtrValue, field)
2019-05-20 17:37:55 +02:00
return
2019-03-14 09:18:23 +01:00
}
2019-08-13 17:20:35 +02:00
utils.TypeMustBe(sliceElemType, reflect.Struct, "jet: unsupported slice element type"+fieldToString(field))
2019-03-14 09:18:23 +01:00
2019-07-13 13:17:28 +02:00
structGroupKey := scanContext.getGroupKey(sliceElemType, field)
2019-03-30 09:59:24 +01:00
2019-07-13 13:17:28 +02:00
groupKey = groupKey + "," + structGroupKey
2019-03-30 09:59:24 +01:00
index, ok := scanContext.uniqueDestObjectsMap[groupKey]
2019-03-30 09:59:24 +01:00
2019-05-20 17:37:55 +02:00
if ok {
structPtrValue := getSliceElemPtrAt(slicePtrValue, index)
2019-03-30 09:59:24 +01:00
return mapRowToStruct(scanContext, groupKey, typesVisited, structPtrValue, field, true)
}
2019-03-30 09:59:24 +01:00
destinationStructPtr := newElemPtrValueForSlice(slicePtrValue)
2019-03-30 09:59:24 +01:00
updated, err = mapRowToStruct(scanContext, groupKey, typesVisited, destinationStructPtr, field)
2019-05-20 17:37:55 +02:00
if err != nil {
return
}
2019-05-22 11:28:32 +02:00
if updated {
scanContext.uniqueDestObjectsMap[groupKey] = slicePtrValue.Elem().Len()
err = appendElemToSlice(slicePtrValue, destinationStructPtr)
if err != nil {
return
2019-05-20 17:37:55 +02:00
}
2019-03-30 09:59:24 +01:00
}
2019-05-20 17:37:55 +02:00
return
}
2019-07-13 13:17:28 +02:00
func mapRowToBaseTypeSlice(scanContext *scanContext, slicePtrValue reflect.Value, field *reflect.StructField) (updated bool, err error) {
index := 0
if field != nil {
typeName, columnName := getTypeAndFieldName("", *field)
if index = scanContext.typeToColumnIndex(typeName, columnName); index < 0 {
return
}
}
rowElemPtr := scanContext.rowElemValuePtr(index)
2019-03-14 09:18:23 +01:00
if rowElemPtr.IsValid() && !rowElemPtr.IsNil() {
2019-07-13 13:17:28 +02:00
updated = true
err = appendElemToSlice(slicePtrValue, rowElemPtr)
2019-07-13 13:17:28 +02:00
if err != nil {
return
}
2019-03-14 09:18:23 +01:00
}
2019-07-13 13:17:28 +02:00
return
2019-03-14 09:18:23 +01:00
}
func mapRowToStruct(
scanContext *scanContext,
groupKey string,
typesVisited *typeStack, // to prevent circular dependency scan
structPtrValue reflect.Value,
parentField *reflect.StructField,
onlySlices ...bool, // small optimization, not to assign to already assigned struct fields
) (updated bool, err error) {
mapOnlySlices := len(onlySlices) > 0
2019-07-13 13:17:28 +02:00
structType := structPtrValue.Type().Elem()
2019-03-14 09:18:23 +01:00
if typesVisited.contains(&structType) {
return false, nil
}
typesVisited.push(&structType)
defer typesVisited.pop()
2019-07-13 13:17:28 +02:00
typeInf := scanContext.getTypeInfo(structType, parentField)
structValue := structPtrValue.Elem()
for i := 0; i < structValue.NumField(); i++ {
field := structType.Field(i)
fieldValue := structValue.Field(i)
2019-07-30 15:04:36 +02:00
if !fieldValue.CanSet() { // private field
continue
}
2019-07-13 13:17:28 +02:00
fieldMap := typeInf.fieldMappings[i]
if fieldMap.complexType {
var changed bool
changed, err = mapRowToDestinationValue(scanContext, groupKey, typesVisited, fieldValue, &field)
2019-07-13 13:17:28 +02:00
if err != nil {
return
}
if changed {
updated = true
}
} else {
if mapOnlySlices || fieldMap.columnIndex == -1 {
2019-07-13 13:17:28 +02:00
continue
}
cellValue := scanContext.rowElem(fieldMap.columnIndex)
2019-07-13 13:17:28 +02:00
if cellValue == nil {
continue
}
2019-07-13 13:17:28 +02:00
initializeValueIfNilPtr(fieldValue)
updated = true
2019-07-13 13:17:28 +02:00
if fieldMap.implementsScanner {
2019-07-13 13:17:28 +02:00
scanner := getScanner(fieldValue)
err = scanner.Scan(cellValue)
if err != nil {
err = fmt.Errorf(`can't scan %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err)
return
2019-07-13 13:17:28 +02:00
}
} else {
err = setReflectValue(reflect.ValueOf(cellValue), fieldValue)
if err != nil {
err = fmt.Errorf(`can't assign %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err)
return
}
2019-07-13 13:17:28 +02:00
}
}
}
return
2019-03-14 09:18:23 +01:00
}
func mapRowToDestinationValue(
scanContext *scanContext,
groupKey string,
typesVisited *typeStack,
dest reflect.Value,
structField *reflect.StructField) (updated bool, err error) {
2019-03-14 09:18:23 +01:00
2019-05-20 17:37:55 +02:00
var destPtrValue reflect.Value
2019-03-14 09:18:23 +01:00
2019-05-20 17:37:55 +02:00
if dest.Kind() != reflect.Ptr {
destPtrValue = dest.Addr()
} else {
2019-05-20 17:37:55 +02:00
if dest.IsNil() {
destPtrValue = reflect.New(dest.Type().Elem())
2019-03-14 09:18:23 +01:00
} else {
2019-05-20 17:37:55 +02:00
destPtrValue = dest
2019-03-14 09:18:23 +01:00
}
}
updated, err = mapRowToDestinationPtr(scanContext, groupKey, typesVisited, destPtrValue, structField)
2019-05-20 17:37:55 +02:00
if err != nil {
return
}
if dest.Kind() == reflect.Ptr && dest.IsNil() && updated {
dest.Set(destPtrValue)
}
return
}
func mapRowToDestinationPtr(
scanContext *scanContext,
groupKey string,
typesVisited *typeStack,
destPtrValue reflect.Value,
structField *reflect.StructField) (updated bool, err error) {
utils.ValueMustBe(destPtrValue, reflect.Ptr, "jet: internal error. Destination is not pointer.")
destValueKind := destPtrValue.Elem().Kind()
if destValueKind == reflect.Struct {
return mapRowToStruct(scanContext, groupKey, typesVisited, destPtrValue, structField)
} else if destValueKind == reflect.Slice {
return mapRowToSlice(scanContext, groupKey, typesVisited, destPtrValue, structField)
} else {
panic("jet: unsupported dest type: " + structField.Name + " " + structField.Type.String())
}
}