open-vault/vendor/github.com/aerospike/aerospike-client-go/marshal.go
Eugene R 331529fc94
Aerospike storage backend (#10131)
* add an Aerospike storage backend

* go mod vendor

* add Aerospike storage configuration docs

* review fixes

* bump aerospike client to v3.1.1

* rename the defaultHostname variable

* relocate the docs page
2021-01-12 15:26:07 -08:00

359 lines
9.2 KiB
Go

// +build !as_performance
// Copyright 2013-2019 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aerospike
import (
"fmt"
"math"
"reflect"
"strings"
"sync"
"time"
)
var aerospikeTag = "as"
const (
aerospikeMetaTag = "asm"
aerospikeMetaTagGen = "gen"
aerospikeMetaTagTTL = "ttl"
)
// This method is copied verbatim from https://golang.org/src/encoding/json/encode.go
// to ensure compatibility with the json package.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
// SetAerospikeTag sets the bin tag to the specified tag.
// This will be useful for when a user wants to use the same tag name for two different concerns.
// For example, one will be able to use the same tag name for both json and aerospike bin name.
func SetAerospikeTag(tag string) {
aerospikeTag = tag
}
func valueToInterface(f reflect.Value, clusterSupportsFloat bool) interface{} {
// get to the core value
for f.Kind() == reflect.Ptr {
if f.IsNil() {
return nil
}
f = reflect.Indirect(f)
}
switch f.Kind() {
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
return IntegerValue(f.Int())
case reflect.Uint64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint16:
return int64(f.Uint())
case reflect.Float64, reflect.Float32:
// support floats through integer encoding if
// server doesn't support floats
if clusterSupportsFloat {
return FloatValue(f.Float())
}
return IntegerValue(math.Float64bits(f.Float()))
case reflect.Struct:
if f.Type().PkgPath() == "time" && f.Type().Name() == "Time" {
return f.Interface().(time.Time).UTC().UnixNano()
}
return structToMap(f, clusterSupportsFloat)
case reflect.Bool:
if f.Bool() {
return IntegerValue(1)
}
return IntegerValue(0)
case reflect.Map:
if f.IsNil() {
return nil
}
newMap := make(map[interface{}]interface{}, f.Len())
for _, mk := range f.MapKeys() {
newMap[valueToInterface(mk, clusterSupportsFloat)] = valueToInterface(f.MapIndex(mk), clusterSupportsFloat)
}
return newMap
case reflect.Slice, reflect.Array:
if f.Kind() == reflect.Slice && f.IsNil() {
return nil
}
if f.Kind() == reflect.Slice && reflect.TypeOf(f.Interface()).Elem().Kind() == reflect.Uint8 {
// handle blobs
return f.Interface().([]byte)
}
// convert to primitives recursively
newSlice := make([]interface{}, f.Len(), f.Cap())
for i := 0; i < len(newSlice); i++ {
newSlice[i] = valueToInterface(f.Index(i), clusterSupportsFloat)
}
return newSlice
case reflect.Interface:
if f.IsNil() {
return nullValue
}
return f.Interface()
default:
return f.Interface()
}
}
func fieldIsMetadata(f reflect.StructField) bool {
meta := f.Tag.Get(aerospikeMetaTag)
return strings.Trim(meta, " ") != ""
}
func fieldIsOmitOnEmpty(f reflect.StructField) bool {
tag := f.Tag.Get(aerospikeTag)
return strings.Contains(tag, ",omitempty")
}
func stripOptions(tag string) string {
i := strings.Index(tag, ",")
if i < 0 {
return tag
}
return string(tag[:i])
}
func fieldAlias(f reflect.StructField) string {
alias := strings.Trim(stripOptions(f.Tag.Get(aerospikeTag)), " ")
if alias != "" {
// if tag is -, the field should not be persisted
if alias == "-" {
return ""
}
return alias
}
return f.Name
}
func setBinMap(s reflect.Value, clusterSupportsFloat bool, typeOfT reflect.Type, binMap BinMap, index []int) {
numFields := typeOfT.NumField()
var fld reflect.StructField
for i := 0; i < numFields; i++ {
fld = typeOfT.Field(i)
fldIndex := append(index, fld.Index...)
if fld.Anonymous && fld.Type.Kind() == reflect.Struct {
setBinMap(s, clusterSupportsFloat, fld.Type, binMap, fldIndex)
continue
}
// skip unexported fields
if fld.PkgPath != "" {
continue
}
if fieldIsMetadata(fld) {
continue
}
// skip transient fields tagged `-`
alias := fieldAlias(fld)
if alias == "" {
continue
}
value := s.FieldByIndex(fldIndex)
if fieldIsOmitOnEmpty(fld) && isEmptyValue(value) {
continue
}
binValue := valueToInterface(value, clusterSupportsFloat)
if _, ok := binMap[alias]; ok {
panic(fmt.Sprintf("ambiguous fields with the same name or alias: %s", alias))
}
binMap[alias] = binValue
}
}
func structToMap(s reflect.Value, clusterSupportsFloat bool) BinMap {
if !s.IsValid() {
return nil
}
var binMap BinMap = make(BinMap, s.NumField())
setBinMap(s, clusterSupportsFloat, s.Type(), binMap, nil)
return binMap
}
func marshal(v interface{}, clusterSupportsFloat bool) BinMap {
s := indirect(reflect.ValueOf(v))
return structToMap(s, clusterSupportsFloat)
}
type syncMap struct {
objectMappings map[reflect.Type]map[string][]int
objectFields map[reflect.Type][]string
objectTTLs map[reflect.Type][][]int
objectGen map[reflect.Type][][]int
mutex sync.RWMutex
}
func (sm *syncMap) setMapping(objType reflect.Type, mapping map[string][]int, fields []string, ttl, gen [][]int) {
sm.mutex.Lock()
sm.objectMappings[objType] = mapping
sm.objectFields[objType] = fields
sm.objectTTLs[objType] = ttl
sm.objectGen[objType] = gen
sm.mutex.Unlock()
}
func indirect(obj reflect.Value) reflect.Value {
for obj.Kind() == reflect.Ptr {
if obj.IsNil() {
return obj
}
obj = obj.Elem()
}
return obj
}
func indirectT(objType reflect.Type) reflect.Type {
for objType.Kind() == reflect.Ptr {
objType = objType.Elem()
}
return objType
}
func (sm *syncMap) mappingExists(objType reflect.Type) (map[string][]int, bool) {
sm.mutex.RLock()
mapping, exists := sm.objectMappings[objType]
sm.mutex.RUnlock()
return mapping, exists
}
func (sm *syncMap) getMapping(objType reflect.Type) map[string][]int {
objType = indirectT(objType)
mapping, exists := sm.mappingExists(objType)
if !exists {
cacheObjectTags(objType)
mapping, _ = sm.mappingExists(objType)
}
return mapping
}
func (sm *syncMap) getMetaMappings(objType reflect.Type) (ttl, gen [][]int) {
objType = indirectT(objType)
if _, exists := sm.mappingExists(objType); !exists {
cacheObjectTags(objType)
}
sm.mutex.RLock()
ttl = sm.objectTTLs[objType]
gen = sm.objectGen[objType]
sm.mutex.RUnlock()
return ttl, gen
}
func (sm *syncMap) fieldsExists(objType reflect.Type) ([]string, bool) {
sm.mutex.RLock()
mapping, exists := sm.objectFields[objType]
sm.mutex.RUnlock()
return mapping, exists
}
func (sm *syncMap) getFields(objType reflect.Type) []string {
objType = indirectT(objType)
fields, exists := sm.fieldsExists(objType)
if !exists {
cacheObjectTags(objType)
fields, _ = sm.fieldsExists(objType)
}
return fields
}
var objectMappings = &syncMap{
objectMappings: map[reflect.Type]map[string][]int{},
objectFields: map[reflect.Type][]string{},
objectTTLs: map[reflect.Type][][]int{},
objectGen: map[reflect.Type][][]int{},
}
func fillMapping(objType reflect.Type, mapping map[string][]int, fields []string, ttl, gen [][]int, index []int) ([]string, [][]int, [][]int) {
numFields := objType.NumField()
for i := 0; i < numFields; i++ {
f := objType.Field(i)
fIndex := append(index, f.Index...)
if f.Anonymous && f.Type.Kind() == reflect.Struct {
fields, ttl, gen = fillMapping(f.Type, mapping, fields, ttl, gen, fIndex)
continue
}
// skip unexported fields
if f.PkgPath != "" {
continue
}
tag := strings.Trim(stripOptions(f.Tag.Get(aerospikeTag)), " ")
tagM := strings.Trim(f.Tag.Get(aerospikeMetaTag), " ")
if tag != "" && tagM != "" {
panic(fmt.Sprintf("Cannot accept both data and metadata tags on the same attribute on struct: %s.%s", objType.Name(), f.Name))
}
if tag != "-" && tagM == "" {
if tag == "" {
tag = f.Name
}
if _, ok := mapping[tag]; ok {
panic(fmt.Sprintf("ambiguous fields with the same name or alias: %s", tag))
}
mapping[tag] = fIndex
fields = append(fields, tag)
}
if tagM == aerospikeMetaTagTTL {
ttl = append(ttl, fIndex)
} else if tagM == aerospikeMetaTagGen {
gen = append(gen, fIndex)
} else if tagM != "" {
panic(fmt.Sprintf("Invalid metadata tag `%s` on struct attribute: %s.%s", tagM, objType.Name(), f.Name))
}
}
return fields, ttl, gen
}
func cacheObjectTags(objType reflect.Type) {
mapping := map[string][]int{}
fields, ttl, gen := fillMapping(objType, mapping, []string{}, nil, nil, nil)
objectMappings.setMapping(objType, mapping, fields, ttl, gen)
}