github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/yarpc/helpers.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package yarpc
    22  
    23  import (
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  	"github.com/uber-go/dosa"
    28  	dosarpc "github.com/uber/dosa-idl/.gen/dosa"
    29  	rpc "go.uber.org/yarpc"
    30  )
    31  
    32  // RawValueAsInterface converts a value from the wire to an object implementing the interface
    33  // based on the dosa type. For example, a TUUID type will get a dosa.UUID object
    34  func RawValueAsInterface(val dosarpc.RawValue, typ dosa.Type) interface{} {
    35  	switch typ {
    36  	case dosa.Blob:
    37  		return val.BinaryValue
    38  	case dosa.TUUID:
    39  		if len(val.BinaryValue) == 0 {
    40  			return (*dosa.UUID)(nil)
    41  		}
    42  		uuid, _ := dosa.BytesToUUID(val.BinaryValue) // TODO: should we handle this error?
    43  		return &uuid
    44  	case dosa.String:
    45  		return val.StringValue
    46  	case dosa.Int32:
    47  		return val.Int32Value
    48  	case dosa.Int64:
    49  		return val.Int64Value
    50  	case dosa.Double:
    51  		return val.DoubleValue
    52  	case dosa.Timestamp:
    53  		if val.Int64Value == nil {
    54  			return (*time.Time)(nil)
    55  		}
    56  		t := time.Unix(0, *val.Int64Value)
    57  		return &t
    58  	case dosa.Bool:
    59  		return val.BoolValue
    60  	}
    61  	panic("bad type")
    62  }
    63  
    64  // RawValueFromInterface takes an interface, introspects the type, and then
    65  // returns a RawValue object that represents this. It panics if the type
    66  // is not in the list, which should be a dosa bug
    67  func RawValueFromInterface(i interface{}) (*dosarpc.RawValue, error) {
    68  	// TODO: Do we do type compatibility checks here? We should know the schema,
    69  	// but the callers are all well known and should match the types
    70  	switch v := i.(type) {
    71  	case string:
    72  		return &dosarpc.RawValue{StringValue: &v}, nil
    73  	case bool:
    74  		return &dosarpc.RawValue{BoolValue: &v}, nil
    75  	case int64:
    76  		return &dosarpc.RawValue{Int64Value: &v}, nil
    77  	case int32:
    78  		return &dosarpc.RawValue{Int32Value: &v}, nil
    79  	case float64:
    80  		return &dosarpc.RawValue{DoubleValue: &v}, nil
    81  	case []byte:
    82  		// If we set nil to BinaryValue, thrift cannot encode it
    83  		// as it thought we didn't set any field in the union
    84  		if v == nil {
    85  			v = []byte{}
    86  		}
    87  		return &dosarpc.RawValue{BinaryValue: v}, nil
    88  	case time.Time:
    89  		time := v.UnixNano()
    90  		return &dosarpc.RawValue{Int64Value: &time}, nil
    91  	case dosa.UUID:
    92  		bytes, err := v.Bytes()
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		return &dosarpc.RawValue{BinaryValue: bytes}, nil
    97  	case *dosa.UUID:
    98  		if v == nil {
    99  			return nil, nil
   100  		}
   101  		bytes, err := v.Bytes()
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		return &dosarpc.RawValue{BinaryValue: bytes}, nil
   106  	case *string:
   107  		if v == nil {
   108  			return nil, nil
   109  		}
   110  		return &dosarpc.RawValue{StringValue: v}, nil
   111  	case *int32:
   112  		if v == nil {
   113  			return nil, nil
   114  		}
   115  		return &dosarpc.RawValue{Int32Value: v}, nil
   116  	case *int64:
   117  		if v == nil {
   118  			return nil, nil
   119  		}
   120  		return &dosarpc.RawValue{Int64Value: v}, nil
   121  	case *float64:
   122  		if v == nil {
   123  			return nil, nil
   124  		}
   125  		return &dosarpc.RawValue{DoubleValue: v}, nil
   126  	case *bool:
   127  		if v == nil {
   128  			return nil, nil
   129  		}
   130  		return &dosarpc.RawValue{BoolValue: v}, nil
   131  	case *time.Time:
   132  		if v == nil {
   133  			return nil, nil
   134  		}
   135  		t := v.UnixNano()
   136  		return &dosarpc.RawValue{Int64Value: &t}, nil
   137  	}
   138  	panic("bad type")
   139  }
   140  
   141  // RPCTypeFromClientType returns the RPC ElemType from a DOSA Type
   142  func RPCTypeFromClientType(t dosa.Type) dosarpc.ElemType {
   143  	switch t {
   144  	case dosa.Bool:
   145  		return dosarpc.ElemTypeBool
   146  	case dosa.Blob:
   147  		return dosarpc.ElemTypeBlob
   148  	case dosa.String:
   149  		return dosarpc.ElemTypeString
   150  	case dosa.Int32:
   151  		return dosarpc.ElemTypeInt32
   152  	case dosa.Int64:
   153  		return dosarpc.ElemTypeInt64
   154  	case dosa.Double:
   155  		return dosarpc.ElemTypeDouble
   156  	case dosa.Timestamp:
   157  		return dosarpc.ElemTypeTimestamp
   158  	case dosa.TUUID:
   159  		return dosarpc.ElemTypeUUID
   160  	}
   161  	panic("bad type")
   162  }
   163  
   164  // RPCTypeToClientType returns the DOSA Type from RPC ElemType
   165  func RPCTypeToClientType(t dosarpc.ElemType) dosa.Type {
   166  	switch t {
   167  	case dosarpc.ElemTypeBool:
   168  		return dosa.Bool
   169  	case dosarpc.ElemTypeBlob:
   170  		return dosa.Blob
   171  	case dosarpc.ElemTypeString:
   172  		return dosa.String
   173  	case dosarpc.ElemTypeInt32:
   174  		return dosa.Int32
   175  	case dosarpc.ElemTypeInt64:
   176  		return dosa.Int64
   177  	case dosarpc.ElemTypeDouble:
   178  		return dosa.Double
   179  	case dosarpc.ElemTypeTimestamp:
   180  		return dosa.Timestamp
   181  	case dosarpc.ElemTypeUUID:
   182  		return dosa.TUUID
   183  	}
   184  	panic("bad type")
   185  }
   186  
   187  // PrimaryKeyToThrift converts the dosa primary key to the thrift primary key type
   188  func PrimaryKeyToThrift(key *dosa.PrimaryKey) *dosarpc.PrimaryKey {
   189  	ck := make([]*dosarpc.ClusteringKey, len(key.ClusteringKeys))
   190  	for ckinx, clusteringKey := range key.ClusteringKeys {
   191  		// TODO: The client uses 'descending' but the RPC uses 'ascending'? Fix this insanity!
   192  		ascending := !clusteringKey.Descending
   193  		name := clusteringKey.Name
   194  		ck[ckinx] = &dosarpc.ClusteringKey{Name: &name, Asc: &ascending}
   195  	}
   196  	return &dosarpc.PrimaryKey{PartitionKeys: key.PartitionKeys, ClusteringKeys: ck}
   197  }
   198  
   199  // EntityDefinitionToThrift converts the client EntityDefinition to the RPC EntityDefinition
   200  func EntityDefinitionToThrift(ed *dosa.EntityDefinition) *dosarpc.EntityDefinition {
   201  	fd := make(map[string]*dosarpc.FieldDesc, len(ed.Columns))
   202  	for _, column := range ed.Columns {
   203  		rpcType := RPCTypeFromClientType(column.Type)
   204  		fd[column.Name] = &dosarpc.FieldDesc{Type: &rpcType}
   205  	}
   206  
   207  	// indexes
   208  	indexes := make(map[string]*dosarpc.IndexDefinition)
   209  	for name, index := range ed.Indexes {
   210  		pkI := PrimaryKeyToThrift(index.Key)
   211  		indexes[name] = &dosarpc.IndexDefinition{Key: pkI}
   212  	}
   213  	return &dosarpc.EntityDefinition{
   214  		PrimaryKey: PrimaryKeyToThrift(ed.Key),
   215  		FieldDescs: fd,
   216  		Name:       &ed.Name,
   217  		Indexes:    indexes,
   218  	}
   219  }
   220  
   221  // FromThriftToPrimaryKey converts thrift primary key type to dosa primary key type
   222  func FromThriftToPrimaryKey(key *dosarpc.PrimaryKey) *dosa.PrimaryKey {
   223  	pk := key.PartitionKeys
   224  	ck := make([]*dosa.ClusteringKey, len(key.ClusteringKeys))
   225  	for i, v := range key.ClusteringKeys {
   226  		ck[i] = &dosa.ClusteringKey{
   227  			Name:       *v.Name,
   228  			Descending: !*v.Asc,
   229  		}
   230  	}
   231  
   232  	return &dosa.PrimaryKey{
   233  		PartitionKeys:  pk,
   234  		ClusteringKeys: ck,
   235  	}
   236  }
   237  
   238  // FromThriftToEntityDefinition converts the RPC EntityDefinition to client EntityDefinition
   239  func FromThriftToEntityDefinition(ed *dosarpc.EntityDefinition) *dosa.EntityDefinition {
   240  	fields := make([]*dosa.ColumnDefinition, len(ed.FieldDescs))
   241  	i := 0
   242  	for k, v := range ed.FieldDescs {
   243  		fields[i] = &dosa.ColumnDefinition{
   244  			Name: k,
   245  			Type: RPCTypeToClientType(*v.Type),
   246  			// TODO Tag
   247  		}
   248  		i++
   249  	}
   250  
   251  	indexes := make(map[string]*dosa.IndexDefinition)
   252  	for name, index := range ed.Indexes {
   253  		indexes[name] = &dosa.IndexDefinition{
   254  			Key: FromThriftToPrimaryKey(index.Key),
   255  		}
   256  	}
   257  
   258  	return &dosa.EntityDefinition{
   259  		Name:    *ed.Name,
   260  		Columns: fields,
   261  		Key:     FromThriftToPrimaryKey(ed.PrimaryKey),
   262  		Indexes: indexes,
   263  	}
   264  }
   265  
   266  func encodeOperator(o dosa.Operator) *dosarpc.Operator {
   267  	var op dosarpc.Operator
   268  	switch o {
   269  	case dosa.Eq:
   270  		op = dosarpc.OperatorEq
   271  	case dosa.Gt:
   272  		op = dosarpc.OperatorGt
   273  	case dosa.GtOrEq:
   274  		op = dosarpc.OperatorGtOrEq
   275  	case dosa.Lt:
   276  		op = dosarpc.OperatorLt
   277  	case dosa.LtOrEq:
   278  		op = dosarpc.OperatorLtOrEq
   279  	}
   280  	return &op
   281  }
   282  
   283  func decodeResults(ei *dosa.EntityInfo, invals dosarpc.FieldValueMap) map[string]dosa.FieldValue {
   284  	result := map[string]dosa.FieldValue{}
   285  	// TODO: create a typemap to make this faster
   286  	for name, value := range invals {
   287  		for _, col := range ei.Def.Columns {
   288  			if col.Name == name {
   289  				result[name] = RawValueAsInterface(*value.ElemValue, col.Type)
   290  				break
   291  			}
   292  		}
   293  	}
   294  	return result
   295  }
   296  
   297  func makeRPCminimumFields(minimumFields []string) map[string]struct{} {
   298  	var rpcminimumFields map[string]struct{}
   299  	if minimumFields != nil {
   300  		rpcminimumFields = map[string]struct{}{}
   301  		for _, field := range minimumFields {
   302  			rpcminimumFields[field] = struct{}{}
   303  		}
   304  	}
   305  	return rpcminimumFields
   306  }
   307  func entityInfoToSchemaRef(ei *dosa.EntityInfo) *dosarpc.SchemaRef {
   308  	scope := ei.Ref.Scope
   309  	namePrefix := ei.Ref.NamePrefix
   310  	entityName := ei.Ref.EntityName
   311  	version := ei.Ref.Version
   312  	sr := dosarpc.SchemaRef{
   313  		Scope:      &scope,
   314  		NamePrefix: &namePrefix,
   315  		EntityName: &entityName,
   316  		Version:    &version,
   317  	}
   318  	return &sr
   319  }
   320  
   321  func fieldValueMapFromClientMap(values map[string]dosa.FieldValue) (dosarpc.FieldValueMap, error) {
   322  	fields := dosarpc.FieldValueMap{}
   323  	for name, value := range values {
   324  		rv, err := RawValueFromInterface(value)
   325  		if err != nil {
   326  			return nil, errors.Wrapf(err, "Error encoding field %q", name)
   327  		}
   328  		if rv == nil {
   329  			continue
   330  		}
   331  		rpcValue := &dosarpc.Value{ElemValue: rv}
   332  		fields[name] = rpcValue
   333  	}
   334  	return fields, nil
   335  }
   336  
   337  // VersionHeader returns the rpc style version header
   338  func VersionHeader() rpc.CallOption {
   339  	return rpc.WithHeader(_version, dosa.VERSION)
   340  }