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