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 }