1// +build !as_performance
2
3// Copyright 2013-2019 Aerospike, Inc.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package aerospike
18
19import (
20	"fmt"
21	"math"
22	"reflect"
23	"strings"
24	"sync"
25	"time"
26)
27
28var aerospikeTag = "as"
29
30const (
31	aerospikeMetaTag    = "asm"
32	aerospikeMetaTagGen = "gen"
33	aerospikeMetaTagTTL = "ttl"
34)
35
36// This method is copied verbatim from https://golang.org/src/encoding/json/encode.go
37// to ensure compatibility with the json package.
38func isEmptyValue(v reflect.Value) bool {
39	switch v.Kind() {
40	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
41		return v.Len() == 0
42	case reflect.Bool:
43		return !v.Bool()
44	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
45		return v.Int() == 0
46	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
47		return v.Uint() == 0
48	case reflect.Float32, reflect.Float64:
49		return v.Float() == 0
50	case reflect.Interface, reflect.Ptr:
51		return v.IsNil()
52	}
53
54	return false
55}
56
57// SetAerospikeTag sets the bin tag to the specified tag.
58// This will be useful for when a user wants to use the same tag name for two different concerns.
59// For example, one will be able to use the same tag name for both json and aerospike bin name.
60func SetAerospikeTag(tag string) {
61	aerospikeTag = tag
62}
63
64func valueToInterface(f reflect.Value, clusterSupportsFloat bool) interface{} {
65	// get to the core value
66	for f.Kind() == reflect.Ptr {
67		if f.IsNil() {
68			return nil
69		}
70		f = reflect.Indirect(f)
71	}
72
73	switch f.Kind() {
74	case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
75		return IntegerValue(f.Int())
76	case reflect.Uint64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint16:
77		return int64(f.Uint())
78	case reflect.Float64, reflect.Float32:
79		// support floats through integer encoding if
80		// server doesn't support floats
81		if clusterSupportsFloat {
82			return FloatValue(f.Float())
83		}
84		return IntegerValue(math.Float64bits(f.Float()))
85
86	case reflect.Struct:
87		if f.Type().PkgPath() == "time" && f.Type().Name() == "Time" {
88			return f.Interface().(time.Time).UTC().UnixNano()
89		}
90		return structToMap(f, clusterSupportsFloat)
91	case reflect.Bool:
92		if f.Bool() {
93			return IntegerValue(1)
94		}
95		return IntegerValue(0)
96	case reflect.Map:
97		if f.IsNil() {
98			return nil
99		}
100
101		newMap := make(map[interface{}]interface{}, f.Len())
102		for _, mk := range f.MapKeys() {
103			newMap[valueToInterface(mk, clusterSupportsFloat)] = valueToInterface(f.MapIndex(mk), clusterSupportsFloat)
104		}
105
106		return newMap
107	case reflect.Slice, reflect.Array:
108		if f.Kind() == reflect.Slice && f.IsNil() {
109			return nil
110		}
111		if f.Kind() == reflect.Slice && reflect.TypeOf(f.Interface()).Elem().Kind() == reflect.Uint8 {
112			// handle blobs
113			return f.Interface().([]byte)
114		}
115		// convert to primitives recursively
116		newSlice := make([]interface{}, f.Len(), f.Cap())
117		for i := 0; i < len(newSlice); i++ {
118			newSlice[i] = valueToInterface(f.Index(i), clusterSupportsFloat)
119		}
120		return newSlice
121	case reflect.Interface:
122		if f.IsNil() {
123			return nullValue
124		}
125		return f.Interface()
126	default:
127		return f.Interface()
128	}
129}
130
131func fieldIsMetadata(f reflect.StructField) bool {
132	meta := f.Tag.Get(aerospikeMetaTag)
133	return strings.Trim(meta, " ") != ""
134}
135
136func fieldIsOmitOnEmpty(f reflect.StructField) bool {
137	tag := f.Tag.Get(aerospikeTag)
138	return strings.Contains(tag, ",omitempty")
139}
140
141func stripOptions(tag string) string {
142	i := strings.Index(tag, ",")
143	if i < 0 {
144		return tag
145	}
146	return string(tag[:i])
147}
148
149func fieldAlias(f reflect.StructField) string {
150	alias := strings.Trim(stripOptions(f.Tag.Get(aerospikeTag)), " ")
151	if alias != "" {
152		// if tag is -, the field should not be persisted
153		if alias == "-" {
154			return ""
155		}
156		return alias
157	}
158	return f.Name
159}
160
161func setBinMap(s reflect.Value, clusterSupportsFloat bool, typeOfT reflect.Type, binMap BinMap, index []int) {
162	numFields := typeOfT.NumField()
163	var fld reflect.StructField
164	for i := 0; i < numFields; i++ {
165		fld = typeOfT.Field(i)
166
167		fldIndex := append(index, fld.Index...)
168
169		if fld.Anonymous && fld.Type.Kind() == reflect.Struct {
170			setBinMap(s, clusterSupportsFloat, fld.Type, binMap, fldIndex)
171			continue
172		}
173
174		// skip unexported fields
175		if fld.PkgPath != "" {
176			continue
177		}
178
179		if fieldIsMetadata(fld) {
180			continue
181		}
182
183		// skip transient fields tagged `-`
184		alias := fieldAlias(fld)
185		if alias == "" {
186			continue
187		}
188
189		value := s.FieldByIndex(fldIndex)
190		if fieldIsOmitOnEmpty(fld) && isEmptyValue(value) {
191			continue
192		}
193
194		binValue := valueToInterface(value, clusterSupportsFloat)
195
196		if _, ok := binMap[alias]; ok {
197			panic(fmt.Sprintf("ambiguous fields with the same name or alias: %s", alias))
198		}
199		binMap[alias] = binValue
200	}
201}
202
203func structToMap(s reflect.Value, clusterSupportsFloat bool) BinMap {
204	if !s.IsValid() {
205		return nil
206	}
207
208	var binMap BinMap = make(BinMap, s.NumField())
209
210	setBinMap(s, clusterSupportsFloat, s.Type(), binMap, nil)
211
212	return binMap
213}
214
215func marshal(v interface{}, clusterSupportsFloat bool) BinMap {
216	s := indirect(reflect.ValueOf(v))
217	return structToMap(s, clusterSupportsFloat)
218}
219
220type syncMap struct {
221	objectMappings map[reflect.Type]map[string][]int
222	objectFields   map[reflect.Type][]string
223	objectTTLs     map[reflect.Type][][]int
224	objectGen      map[reflect.Type][][]int
225	mutex          sync.RWMutex
226}
227
228func (sm *syncMap) setMapping(objType reflect.Type, mapping map[string][]int, fields []string, ttl, gen [][]int) {
229	sm.mutex.Lock()
230	sm.objectMappings[objType] = mapping
231	sm.objectFields[objType] = fields
232	sm.objectTTLs[objType] = ttl
233	sm.objectGen[objType] = gen
234	sm.mutex.Unlock()
235}
236
237func indirect(obj reflect.Value) reflect.Value {
238	for obj.Kind() == reflect.Ptr {
239		if obj.IsNil() {
240			return obj
241		}
242		obj = obj.Elem()
243	}
244	return obj
245}
246
247func indirectT(objType reflect.Type) reflect.Type {
248	for objType.Kind() == reflect.Ptr {
249		objType = objType.Elem()
250	}
251	return objType
252}
253
254func (sm *syncMap) mappingExists(objType reflect.Type) (map[string][]int, bool) {
255	sm.mutex.RLock()
256	mapping, exists := sm.objectMappings[objType]
257	sm.mutex.RUnlock()
258	return mapping, exists
259}
260
261func (sm *syncMap) getMapping(objType reflect.Type) map[string][]int {
262	objType = indirectT(objType)
263	mapping, exists := sm.mappingExists(objType)
264	if !exists {
265		cacheObjectTags(objType)
266		mapping, _ = sm.mappingExists(objType)
267	}
268
269	return mapping
270}
271
272func (sm *syncMap) getMetaMappings(objType reflect.Type) (ttl, gen [][]int) {
273	objType = indirectT(objType)
274	if _, exists := sm.mappingExists(objType); !exists {
275		cacheObjectTags(objType)
276	}
277
278	sm.mutex.RLock()
279	ttl = sm.objectTTLs[objType]
280	gen = sm.objectGen[objType]
281	sm.mutex.RUnlock()
282	return ttl, gen
283}
284
285func (sm *syncMap) fieldsExists(objType reflect.Type) ([]string, bool) {
286	sm.mutex.RLock()
287	mapping, exists := sm.objectFields[objType]
288	sm.mutex.RUnlock()
289	return mapping, exists
290}
291
292func (sm *syncMap) getFields(objType reflect.Type) []string {
293	objType = indirectT(objType)
294	fields, exists := sm.fieldsExists(objType)
295	if !exists {
296		cacheObjectTags(objType)
297		fields, _ = sm.fieldsExists(objType)
298	}
299
300	return fields
301}
302
303var objectMappings = &syncMap{
304	objectMappings: map[reflect.Type]map[string][]int{},
305	objectFields:   map[reflect.Type][]string{},
306	objectTTLs:     map[reflect.Type][][]int{},
307	objectGen:      map[reflect.Type][][]int{},
308}
309
310func fillMapping(objType reflect.Type, mapping map[string][]int, fields []string, ttl, gen [][]int, index []int) ([]string, [][]int, [][]int) {
311	numFields := objType.NumField()
312	for i := 0; i < numFields; i++ {
313		f := objType.Field(i)
314		fIndex := append(index, f.Index...)
315		if f.Anonymous && f.Type.Kind() == reflect.Struct {
316			fields, ttl, gen = fillMapping(f.Type, mapping, fields, ttl, gen, fIndex)
317			continue
318		}
319
320		// skip unexported fields
321		if f.PkgPath != "" {
322			continue
323		}
324
325		tag := strings.Trim(stripOptions(f.Tag.Get(aerospikeTag)), " ")
326		tagM := strings.Trim(f.Tag.Get(aerospikeMetaTag), " ")
327
328		if tag != "" && tagM != "" {
329			panic(fmt.Sprintf("Cannot accept both data and metadata tags on the same attribute on struct: %s.%s", objType.Name(), f.Name))
330		}
331
332		if tag != "-" && tagM == "" {
333			if tag == "" {
334				tag = f.Name
335			}
336			if _, ok := mapping[tag]; ok {
337				panic(fmt.Sprintf("ambiguous fields with the same name or alias: %s", tag))
338			}
339			mapping[tag] = fIndex
340			fields = append(fields, tag)
341		}
342
343		if tagM == aerospikeMetaTagTTL {
344			ttl = append(ttl, fIndex)
345		} else if tagM == aerospikeMetaTagGen {
346			gen = append(gen, fIndex)
347		} else if tagM != "" {
348			panic(fmt.Sprintf("Invalid metadata tag `%s` on struct attribute: %s.%s", tagM, objType.Name(), f.Name))
349		}
350	}
351	return fields, ttl, gen
352}
353
354func cacheObjectTags(objType reflect.Type) {
355	mapping := map[string][]int{}
356	fields, ttl, gen := fillMapping(objType, mapping, []string{}, nil, nil, nil)
357	objectMappings.setMapping(objType, mapping, fields, ttl, gen)
358}
359