-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathrecord_description.go
151 lines (131 loc) · 4.48 KB
/
record_description.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package godb
import (
"fmt"
"reflect"
"strings"
"github.com/samonzeweb/godb/dbreflect"
)
// recordDescription describes the source or target of a SQL statement.
// The record (source or target) could be a struct pointer, or slice of structs,
// or a slice of pointers to structs.
type recordDescription struct {
// record is always a pointer
record interface{}
instanceType reflect.Type
structMapping *dbreflect.StructMapping
isSlice bool
isSliceOfPointers bool
}
// tableNamer wraps the TableName method, allowing a struct to specify a
// corresponding table name in database.
type tableNamer interface {
TableName() string
}
// buildRecordDescription builds a recordDescription for the given object.
// Always use a pointer as argument.
func buildRecordDescription(record interface{}) (*recordDescription, error) {
recordDesc := &recordDescription{}
recordDesc.record = record
recordType := reflect.TypeOf(record)
if recordType.Kind() != reflect.Ptr {
return nil, fmt.Errorf("invalid argument, need a pointer, got a %s", recordType.Kind())
}
recordType = recordType.Elem()
// A record could be a slice, or a single instance
if recordType.Kind() == reflect.Slice {
// Slice
recordDesc.isSlice = true
recordDesc.isSliceOfPointers = false
recordType = recordType.Elem()
if recordType.Kind() == reflect.Ptr {
// Slice of pointers
recordType = recordType.Elem()
recordDesc.isSliceOfPointers = true
}
} else {
// Single instance
recordDesc.isSlice = false
recordDesc.isSliceOfPointers = false
}
if recordType.Kind() != reflect.Struct {
return nil, fmt.Errorf("invalid argument, need a struct or structs slice, got a (or slice of) %s", recordType.Kind())
}
var err error
recordDesc.instanceType = recordType
recordDesc.structMapping, err = dbreflect.Cache.GetOrCreateStructMapping(recordType)
if err != nil {
return nil, err
}
return recordDesc, nil
}
// fillRecord build if needed new record instance and call the given function
// with the current record.
// If the record is a single instante it just use its pointer.
// If the recod is a slice, it creates new instances and adds it to the slice.
func (r *recordDescription) fillRecord(f func(record interface{}) error) error {
if !r.isSlice {
return f(r.record)
}
// It's a slice
// Create a new instance (reflect.Value of a pointer of the type needed)
newInstancePointerValue := reflect.New(r.instanceType)
newInstancePointer := newInstancePointerValue.Interface()
// Call func with the struct pointer
err := f(newInstancePointer)
if err != nil {
return err
}
// Add the new instance to the struct
// Get the current slice (r.record is a slice pointer)
sliceValue := reflect.ValueOf(r.record).Elem()
// Add the new instance (or pointer to) into the slice
instanceOrPointerValue := newInstancePointerValue
if !r.isSliceOfPointers {
instanceOrPointerValue = newInstancePointerValue.Elem()
}
newSliceValue := reflect.Append(sliceValue, instanceOrPointerValue)
// Update the content of r.record with the new slice
reflect.ValueOf(r.record).Elem().Set(newSliceValue)
return nil
}
// getOneInstancePointer returns an instance pointers of the record (or record
// part) to be used for interface check and method call.
// Don't use the instance pointer for other use, don't change values,
// don't store it for later use, ...
func (r *recordDescription) getOneInstancePointer() interface{} {
if !r.isSlice {
return r.record
}
return reflect.New(r.instanceType).Interface()
}
// len returns the len of the record.
// If it is a slice, it returns the slice length, otherwise it returns 1 (for
// a single instance).
func (r *recordDescription) len() int {
if !r.isSlice {
return 1
}
return reflect.Indirect(reflect.ValueOf(r.record)).Len()
}
// index returns the pointer to the record having the given index.
func (r *recordDescription) index(i int) interface{} {
if !r.isSlice {
return r.record
}
slice := reflect.Indirect(reflect.ValueOf(r.record))
v := slice.Index(i)
if v.Type().Kind() == reflect.Ptr {
return v.Interface()
}
return v.Addr().Interface()
}
// getTableName returns the table name to use for the current record and
// if model's TableName() is used to get name it returns true, else false
func (r *recordDescription) getTableName() (string, bool) {
p := r.getOneInstancePointer()
if namer, ok := p.(tableNamer); ok {
return namer.TableName(), true
}
typeNameParts := strings.Split(r.structMapping.Name, ".")
return typeNameParts[len(typeNameParts)-1], false
}