Ejemplo n.º 1
0
Archivo: builder.go Proyecto: upper/db
// Map receives a pointer to map or struct and maps it to columns and values.
func Map(item interface{}, options *MapOptions) ([]string, []interface{}, error) {
	var fv fieldValue

	if options == nil {
		options = &defaultMapOptions
	}

	itemV := reflect.ValueOf(item)
	if !itemV.IsValid() {
		return nil, nil, nil
	}

	itemT := itemV.Type()

	if itemT.Kind() == reflect.Ptr {
		// Single dereference. Just in case the user passes a pointer to struct
		// instead of a struct.
		item = itemV.Elem().Interface()
		itemV = reflect.ValueOf(item)
		itemT = itemV.Type()
	}

	switch itemT.Kind() {

	case reflect.Struct:

		fieldMap := mapper.TypeMap(itemT).Names
		nfields := len(fieldMap)

		fv.values = make([]interface{}, 0, nfields)
		fv.fields = make([]string, 0, nfields)

		for _, fi := range fieldMap {

			// Field options
			_, tagOmitEmpty := fi.Options["omitempty"]
			_, tagStringArray := fi.Options["stringarray"]
			_, tagInt64Array := fi.Options["int64array"]
			_, tagJSONB := fi.Options["jsonb"]

			fld := reflectx.FieldByIndexesReadOnly(itemV, fi.Index)
			if fld.Kind() == reflect.Ptr && fld.IsNil() {
				if options.IncludeNil || !tagOmitEmpty {
					fv.fields = append(fv.fields, fi.Name)
					fv.values = append(fv.values, fld.Interface())
				}
				continue
			}

			var value interface{}
			switch {
			case tagStringArray:
				v, ok := fld.Interface().([]string)
				if !ok {
					return nil, nil, fmt.Errorf(`Expecting field %q to be []string (using "stringarray" tag)`, fi.Name)
				}
				value = stringArray(v)
			case tagInt64Array:
				v, ok := fld.Interface().([]int64)
				if !ok {
					return nil, nil, fmt.Errorf(`Expecting field %q to be []int64 (using "int64array" tag)`, fi.Name)
				}
				value = int64Array(v)
			case tagJSONB:
				value = jsonbType{fld.Interface()}
			default:
				value = fld.Interface()
			}

			if !options.IncludeZeroed {
				if tagOmitEmpty {
					if t, ok := fld.Interface().(hasIsZero); ok {
						if t.IsZero() {
							continue
						}
					} else if fld.Kind() == reflect.Array || fld.Kind() == reflect.Slice {
						if value == nil {
							continue
						}
					} else if value == fi.Zero.Interface() {
						continue
					}
				}
			}

			fv.fields = append(fv.fields, fi.Name)
			v, err := marshal(value)
			if err != nil {
				return nil, nil, err
			}
			fv.values = append(fv.values, v)
		}

	case reflect.Map:
		nfields := itemV.Len()
		fv.values = make([]interface{}, nfields)
		fv.fields = make([]string, nfields)
		mkeys := itemV.MapKeys()

		for i, keyV := range mkeys {
			valv := itemV.MapIndex(keyV)
			fv.fields[i] = fmt.Sprintf("%v", keyV.Interface())

			v, err := marshal(valv.Interface())
			if err != nil {
				return nil, nil, err
			}

			fv.values[i] = v
		}
	default:
		return nil, nil, ErrExpectingPointerToEitherMapOrStruct
	}

	if len(fv.fields) == 0 {
		return nil, nil, errors.New("No values mapped.")
	}

	sort.Sort(&fv)

	return fv.fields, fv.values, nil
}
Ejemplo n.º 2
0
Archivo: fetch.go Proyecto: upper/db
func fetchResult(itemT reflect.Type, rows *sql.Rows, columns []string) (reflect.Value, error) {
	var item reflect.Value
	var err error

	objT := itemT

	switch objT.Kind() {
	case reflect.Map:
		item = reflect.MakeMap(objT)
	case reflect.Struct:
		item = reflect.New(objT)
	case reflect.Ptr:
		objT = itemT.Elem()
		if objT.Kind() != reflect.Struct {
			return item, ErrExpectingMapOrStruct
		}
		item = reflect.New(objT)
	default:
		return item, ErrExpectingMapOrStruct
	}

	switch objT.Kind() {

	case reflect.Struct:

		values := make([]interface{}, len(columns))
		typeMap := mapper.TypeMap(itemT)
		fieldMap := typeMap.Names
		wrappedValues := map[*reflectx.FieldInfo]interface{}{}

		for i, k := range columns {
			fi, ok := fieldMap[k]
			if !ok {
				values[i] = new(interface{})
				continue
			}

			// TODO: refactor into a nice pattern
			if _, ok := fi.Options["stringarray"]; ok {
				values[i] = &[]byte{}
				wrappedValues[fi] = values[i]
			} else if _, ok := fi.Options["int64array"]; ok {
				values[i] = &[]byte{}
				wrappedValues[fi] = values[i]
			} else if _, ok := fi.Options["jsonb"]; ok {
				values[i] = &[]byte{}
				wrappedValues[fi] = values[i]
			} else {
				f := reflectx.FieldByIndexes(item, fi.Index)
				values[i] = f.Addr().Interface()
			}
			if u, ok := values[i].(db.Unmarshaler); ok {
				values[i] = scanner{u}
			}
		}

		// Scanner - for reads
		// Valuer  - for writes

		// OptionTypes
		// - before/after scan
		// - before/after valuer..

		if err = rows.Scan(values...); err != nil {
			return item, err
		}

		// TODO: move this stuff out of here.. find a nice pattern
		for fi, v := range wrappedValues {
			var opt string
			if _, ok := fi.Options["stringarray"]; ok {
				opt = "stringarray"
			} else if _, ok := fi.Options["int64array"]; ok {
				opt = "int64array"
			} else if _, ok := fi.Options["jsonb"]; ok {
				opt = "jsonb"
			}

			b := v.(*[]byte)

			f := reflectx.FieldByIndexesReadOnly(item, fi.Index)

			switch opt {
			case "stringarray":
				v := stringArray{}
				err := v.Scan(*b)
				if err != nil {
					return item, err
				}
				f.Set(reflect.ValueOf(v))
			case "int64array":
				v := int64Array{}
				err := v.Scan(*b)
				if err != nil {
					return item, err
				}
				f.Set(reflect.ValueOf(v))
			case "jsonb":
				if len(*b) == 0 {
					continue
				}

				var vv reflect.Value
				t := reflect.PtrTo(f.Type())

				switch t.Kind() {
				case reflect.Map:
					vv = reflect.MakeMap(t)
				case reflect.Slice:
					vv = reflect.MakeSlice(t, 0, 0)
				default:
					vv = reflect.New(t)
				}

				err := json.Unmarshal(*b, vv.Interface())
				if err != nil {
					return item, err
				}

				vv = vv.Elem().Elem()

				if !vv.IsValid() || (vv.Kind() == reflect.Ptr && vv.IsNil()) {
					continue
				}

				f.Set(vv)
			}
		}

	case reflect.Map:

		columns, err := rows.Columns()
		if err != nil {
			return item, err
		}

		values := make([]interface{}, len(columns))
		for i := range values {
			if itemT.Elem().Kind() == reflect.Interface {
				values[i] = new(interface{})
			} else {
				values[i] = reflect.New(itemT.Elem()).Interface()
			}
		}

		if err = rows.Scan(values...); err != nil {
			return item, err
		}

		for i, column := range columns {
			item.SetMapIndex(reflect.ValueOf(column), reflect.Indirect(reflect.ValueOf(values[i])))
		}

	}

	return item, nil
}