open-vault/vendor/github.com/aerospike/aerospike-client-go/client_reflect.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

294 lines
10 KiB
Go

// +build !as_performance
// Copyright 2013-2020 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 (
"errors"
"reflect"
. "github.com/aerospike/aerospike-client-go/internal/atomic"
. "github.com/aerospike/aerospike-client-go/types"
xornd "github.com/aerospike/aerospike-client-go/types/rand"
)
// PutObject writes record bin(s) to the server.
// The policy specifies the transaction timeout, record expiration and how the transaction is
// handled when the record already exists.
// If the policy is nil, the default relevant policy will be used.
// A struct can be tagged to influence the way the object is put in the database:
//
// type Person struct {
// TTL uint32 `asm:"ttl"`
// RecGen uint32 `asm:"gen"`
// Name string `as:"name"`
// Address string `as:"desc,omitempty"`
// Age uint8 `as:",omitempty"`
// Password string `as:"-"`
// }
//
// Tag `as:` denotes Aerospike fields. The first value will be the alias for the field.
// `,omitempty` (without any spaces between the comma and the word) will act like the
// json package, and will not send the value of the field to the database if the value is zero value.
// Tag `asm:` denotes Aerospike Meta fields, and includes ttl and generation values.
// If a tag is marked with `-`, it will not be sent to the database at all.
// Note: Tag `as` can be replaced with any other user-defined tag via the function `SetAerospikeTag`.
func (clnt *Client) PutObject(policy *WritePolicy, key *Key, obj interface{}) (err error) {
policy = clnt.getUsableWritePolicy(policy)
binMap := marshal(obj, clnt.cluster.supportsFloat.Get())
command, err := newWriteCommand(clnt.cluster, policy, key, nil, binMap, _WRITE)
if err != nil {
return err
}
res := command.Execute()
return res
}
// GetObject reads a record for specified key and puts the result into the provided object.
// The policy can be used to specify timeouts.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) GetObject(policy *BasePolicy, key *Key, obj interface{}) error {
policy = clnt.getUsablePolicy(policy)
rval := reflect.ValueOf(obj)
binNames := objectMappings.getFields(rval.Type())
command, err := newReadCommand(clnt.cluster, policy, key, binNames, nil)
if err != nil {
return err
}
command.object = &rval
return command.Execute()
}
// BatchGetObject reads multiple record headers and bins for specified keys in one batch request.
// The returned objects are in positional order with the original key array order.
// If a key is not found, the positional object will not change, and the positional found boolean will be false.
// The policy can be used to specify timeouts.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) BatchGetObjects(policy *BatchPolicy, keys []*Key, objects []interface{}) (found []bool, err error) {
policy = clnt.getUsableBatchPolicy(policy)
// check the size of key and objects
if len(keys) != len(objects) {
return nil, errors.New("wrong number of arguments to BatchGetObjects: number of keys and objects do not match")
}
if len(keys) == 0 {
return nil, errors.New("wrong number of arguments to BatchGetObjects: keys are empty")
}
binSet := map[string]struct{}{}
objectsVal := make([]*reflect.Value, len(objects))
for i := range objects {
rval := reflect.ValueOf(objects[i])
objectsVal[i] = &rval
for _, bn := range objectMappings.getFields(rval.Type()) {
binSet[bn] = struct{}{}
}
}
binNames := make([]string, 0, len(binSet))
for binName := range binSet {
binNames = append(binNames, binName)
}
objectsFound := make([]bool, len(keys))
cmd := newBatchCommandGet(nil, nil, policy, keys, binNames, nil, _INFO1_READ)
cmd.objects = objectsVal
cmd.objectsFound = objectsFound
batchNodes, err := newBatchNodeList(clnt.cluster, policy, keys)
if err != nil {
return nil, err
}
err, filteredOut := clnt.batchExecute(policy, batchNodes, cmd)
if err != nil {
return nil, err
}
if filteredOut > 0 {
err = ErrFilteredOut
}
return objectsFound, err
}
// ScanAllObjects reads all records in specified namespace and set from all nodes.
// If the policy's concurrentNodes is specified, each server node will be read in
// parallel. Otherwise, server nodes are read sequentially.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) ScanAllObjects(apolicy *ScanPolicy, objChan interface{}, namespace string, setName string, binNames ...string) (*Recordset, error) {
policy := *clnt.getUsableScanPolicy(apolicy)
nodes := clnt.cluster.GetNodes()
if len(nodes) == 0 {
return nil, NewAerospikeError(SERVER_NOT_AVAILABLE, "Scan failed because cluster is empty.")
}
clusterKey := int64(0)
if policy.FailOnClusterChange {
var err error
clusterKey, err = queryValidateBegin(nodes[0], namespace)
if err != nil {
return nil, err
}
}
first := NewAtomicBool(true)
// result recordset
taskID := uint64(xornd.Int64())
res := &Recordset{
objectset: *newObjectset(reflect.ValueOf(objChan), len(nodes), taskID),
}
// the whole call should be wrapped in a goroutine
if policy.ConcurrentNodes {
for _, node := range nodes {
go func(node *Node, first bool) {
// Errors are handled inside the command itself
clnt.scanNodeObjects(&policy, node, res, namespace, setName, taskID, clusterKey, first, binNames...)
}(node, first.CompareAndToggle(true))
}
} else {
// scan nodes one by one
go func() {
for _, node := range nodes {
// Errors are handled inside the command itself
clnt.scanNodeObjects(&policy, node, res, namespace, setName, taskID, clusterKey, first.CompareAndToggle(true), binNames...)
}
}()
}
return res, nil
}
// scanNodeObjects reads all records in specified namespace and set for one node only,
// and marshalls the results into the objects of the provided channel in Recordset.
// If the policy is nil, the default relevant policy will be used.
// The resulting records will be marshalled into the objChan.
// objChan will be closed after all the records are read.
func (clnt *Client) ScanNodeObjects(apolicy *ScanPolicy, node *Node, objChan interface{}, namespace string, setName string, binNames ...string) (*Recordset, error) {
policy := *clnt.getUsableScanPolicy(apolicy)
clusterKey := int64(0)
if policy.FailOnClusterChange {
var err error
clusterKey, err = queryValidateBegin(node, namespace)
if err != nil {
return nil, err
}
}
// results channel must be async for performance
taskID := uint64(xornd.Int64())
res := &Recordset{
objectset: *newObjectset(reflect.ValueOf(objChan), 1, taskID),
}
go clnt.scanNodeObjects(&policy, node, res, namespace, setName, taskID, clusterKey, true, binNames...)
return res, nil
}
// scanNodeObjects reads all records in specified namespace and set for one node only,
// and marshalls the results into the objects of the provided channel in Recordset.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) scanNodeObjects(policy *ScanPolicy, node *Node, recordset *Recordset, namespace string, setName string, taskID uint64, clusterKey int64, first bool, binNames ...string) error {
command := newScanObjectsCommand(node, policy, namespace, setName, binNames, recordset, taskID, clusterKey, first)
return command.Execute()
}
// QueryNodeObjects executes a query on all nodes in the cluster and marshals the records into the given channel.
// The query executor puts records on the channel from separate goroutines.
// The caller can concurrently pop objects.
//
// This method is only supported by Aerospike 3+ servers.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) QueryObjects(policy *QueryPolicy, statement *Statement, objChan interface{}) (*Recordset, error) {
policy = clnt.getUsableQueryPolicy(policy)
nodes := clnt.cluster.GetNodes()
if len(nodes) == 0 {
return nil, NewAerospikeError(SERVER_NOT_AVAILABLE, "Query failed because cluster is empty.")
}
clusterKey := int64(0)
if policy.FailOnClusterChange {
var err error
clusterKey, err = queryValidateBegin(nodes[0], statement.Namespace)
if err != nil {
return nil, err
}
}
first := NewAtomicBool(true)
// results channel must be async for performance
recSet := &Recordset{
objectset: *newObjectset(reflect.ValueOf(objChan), len(nodes), statement.TaskId),
}
// the whole call sho
// results channel must be async for performance
for _, node := range nodes {
// copy policies to avoid race conditions
newPolicy := *policy
command := newQueryObjectsCommand(node, &newPolicy, statement, recSet, clusterKey, first.CompareAndToggle(true))
go func() {
// Do not send the error to the channel; it is already handled in the Execute method
command.Execute()
}()
}
return recSet, nil
}
// QueryNodeObjects executes a query on a specific node and marshals the records into the given channel.
// The caller can concurrently pop records off the channel.
//
// This method is only supported by Aerospike 3+ servers.
// If the policy is nil, the default relevant policy will be used.
func (clnt *Client) QueryNodeObjects(policy *QueryPolicy, node *Node, statement *Statement, objChan interface{}) (*Recordset, error) {
policy = clnt.getUsableQueryPolicy(policy)
// results channel must be async for performance
recSet := &Recordset{
objectset: *newObjectset(reflect.ValueOf(objChan), 1, statement.TaskId),
}
clusterKey := int64(0)
if policy.FailOnClusterChange {
var err error
clusterKey, err = queryValidateBegin(node, statement.Namespace)
if err != nil {
return nil, err
}
}
// copy policies to avoid race conditions
newPolicy := *policy
command := newQueryRecordCommand(node, &newPolicy, statement, recSet, clusterKey, true)
go func() {
command.Execute()
}()
return recSet, nil
}