forked from go-rel/rel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
association_meta.go
182 lines (152 loc) · 3.93 KB
/
association_meta.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package rel
import (
"reflect"
"sync"
"github.com/serenize/snaker"
)
var associationMetaCache sync.Map
type associationKey struct {
rt reflect.Type
// string repr of index, because []int is not hashable
index string
}
// AssociationType defines the type of association in database.
type AssociationType uint8
const (
// BelongsTo association.
BelongsTo = iota
// HasOne association.
HasOne
// HasMany association.
HasMany
)
type cachedAssociationMeta struct {
typ AssociationType
targetIndex []int
referenceField string
referenceIndex []int
foreignField string
foreignIndex []int
through string
autoload bool
autosave bool
}
type AssociationMeta struct {
rt reflect.Type
cachedAssociationMeta
}
// Type of association.
func (am AssociationMeta) Type() AssociationType {
return am.typ
}
// ReferenceField of the association.
func (am AssociationMeta) ReferenceField() string {
return am.referenceField
}
// ForeignField of the association.
func (am AssociationMeta) ForeignField() string {
return am.foreignField
}
// Through return intermediary association.
func (am AssociationMeta) Through() string {
return am.through
}
// Autoload assoc setting when parent is loaded.
func (am AssociationMeta) Autoload() bool {
return am.autoload
}
// Autosave setting when parent is created/updated/deleted.
func (am AssociationMeta) Autosave() bool {
return am.autosave
}
// Document returns association target document meta.
func (am AssociationMeta) DocumentMeta() DocumentMeta {
var (
rt = am.rt.FieldByIndex(am.targetIndex).Type
)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
if rt.Kind() == reflect.Slice {
rt = rt.Elem()
}
return getDocumentMeta(rt, false)
}
func getAssociationMeta(rt reflect.Type, index []int) AssociationMeta {
var (
key = associationKey{
rt: rt,
index: encodeIndices(index),
}
)
if val, cached := associationMetaCache.Load(key); cached {
return AssociationMeta{
rt: rt,
cachedAssociationMeta: val.(cachedAssociationMeta),
}
}
var (
sf = rt.FieldByIndex(index)
ft = sf.Type
ref = sf.Tag.Get("ref")
fk = sf.Tag.Get("fk")
fName, _ = fieldName(sf)
assocMeta = cachedAssociationMeta{
targetIndex: index,
through: sf.Tag.Get("through"),
autoload: sf.Tag.Get("auto") == "true" || sf.Tag.Get("autoload") == "true",
autosave: sf.Tag.Get("auto") == "true" || sf.Tag.Get("autosave") == "true",
}
)
if assocMeta.autosave && assocMeta.through != "" {
panic("rel: autosave is not supported for has one/has many through association")
}
for ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice {
ft = ft.Elem()
}
var (
refDocMeta = getDocumentMeta(rt, true)
fkDocMeta = getDocumentMeta(ft, true)
)
// Try to guess ref and fk if not defined.
if ref == "" || fk == "" {
// TODO: replace "id" with inferred primary field
if assocMeta.through != "" {
ref = "id"
fk = "id"
} else if _, isBelongsTo := refDocMeta.index[fName+"_id"]; isBelongsTo {
ref = fName + "_id"
fk = "id"
} else {
ref = "id"
fk = snaker.CamelToSnake(rt.Name()) + "_id"
}
}
if id, exist := refDocMeta.index[ref]; !exist {
panic("rel: references (" + ref + ") field not found ")
} else {
assocMeta.referenceIndex = id
assocMeta.referenceField = ref
}
if id, exist := fkDocMeta.index[fk]; !exist {
panic("rel: foreign_key (" + fk + ") field not found")
} else {
assocMeta.foreignIndex = id
assocMeta.foreignField = fk
}
// guess assoc type
if sf.Type.Kind() == reflect.Slice || (sf.Type.Kind() == reflect.Ptr && sf.Type.Elem().Kind() == reflect.Slice) {
assocMeta.typ = HasMany
} else {
if len(assocMeta.referenceField) > len(assocMeta.foreignField) {
assocMeta.typ = BelongsTo
} else {
assocMeta.typ = HasOne
}
}
associationMetaCache.Store(key, assocMeta)
return AssociationMeta{
rt: rt,
cachedAssociationMeta: assocMeta,
}
}