github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/registry.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 dosa 22 23 import ( 24 "reflect" 25 26 "github.com/pkg/errors" 27 ) 28 29 // RegisteredEntity is the structure that holds all information necessary for 30 // performing operations on an entity as well as helper methods for accessing 31 // type data so that reflection can be minimized. 32 type RegisteredEntity struct { 33 scope string 34 prefix string 35 table *Table 36 info *EntityInfo 37 typ reflect.Type // optimization to avoid doing repetitive reflect.TypeOf 38 } 39 40 // NewRegisteredEntity is a constructor for creating a RegisteredEntity 41 func NewRegisteredEntity(scope, prefix string, table *Table) *RegisteredEntity { 42 // build entity info up-front since version will need to be set soon after 43 // the registry is initialized 44 info := &EntityInfo{ 45 Ref: &SchemaRef{ 46 Scope: scope, 47 NamePrefix: prefix, 48 EntityName: table.Name, 49 }, 50 Def: &table.EntityDefinition, 51 } 52 return &RegisteredEntity{ 53 scope: scope, 54 prefix: prefix, 55 table: table, 56 info: info, 57 } 58 } 59 60 // SetVersion sets the current schema version on the registered entity. 61 func (e *RegisteredEntity) SetVersion(v int32) { 62 e.info.Ref.Version = v 63 } 64 65 // EntityInfo is a helper for accessing the registered entity's EntityInfo 66 // instance which is required by clients to call connector methods. 67 func (e *RegisteredEntity) EntityInfo() *EntityInfo { 68 return e.info 69 } 70 71 // SchemaRef is a helper for accessing the registered entity's 72 // SchemaRef instance. 73 func (e *RegisteredEntity) SchemaRef() *SchemaRef { 74 return e.info.Ref 75 } 76 77 // EntityDefinition is a helper for accessing the registered entity's 78 // EntityDefinition instance. 79 func (e *RegisteredEntity) EntityDefinition() *EntityDefinition { 80 return e.info.Def 81 } 82 83 // KeyFieldValues is a helper for generating a map of field values to be used in a query. 84 func (e *RegisteredEntity) KeyFieldValues(entity DomainObject) map[string]FieldValue { 85 v := reflect.ValueOf(entity).Elem() 86 fieldValues := make(map[string]FieldValue) 87 88 // populate partition key values 89 for _, pk := range e.table.Key.PartitionKeys { 90 fieldName := e.table.ColToField[pk] 91 value := v.FieldByName(fieldName) 92 fieldValues[pk] = value.Interface() 93 } 94 95 // populate clustering key values 96 for _, ck := range e.table.Key.ClusteringKeys { 97 fieldName := e.table.ColToField[ck.Name] 98 value := v.FieldByName(fieldName) 99 if !value.IsValid() { 100 // this should never happen 101 panic("Field " + fieldName + " is not a valid field for " + e.table.StructName) 102 } 103 fieldValues[ck.Name] = value.Interface() 104 } 105 106 return fieldValues 107 } 108 109 // OnlyFieldValues is a helper for generating a map of field values for a 110 // a subset of fields. If a field name provided does not map to an entity 111 // field, an error will be returned. 112 func (e *RegisteredEntity) OnlyFieldValues(entity DomainObject, fieldNames []string) (map[string]FieldValue, error) { 113 if fieldNames == nil || len(fieldNames) == 0 { 114 for _, field := range e.table.ColToField { 115 fieldNames = append(fieldNames, field) 116 } 117 } 118 v := reflect.ValueOf(entity).Elem() 119 fieldValues := make(map[string]FieldValue) 120 121 for _, fieldName := range fieldNames { 122 columnName, ok := e.table.FieldToCol[fieldName] 123 if !ok { 124 return nil, errors.Errorf("%s is not a valid field for %s", fieldName, e.table.StructName) 125 } 126 value := v.FieldByName(fieldName) 127 if !value.IsValid() { 128 // this should never happen 129 panic("Field " + fieldName + " is not a valid field for " + e.table.StructName) 130 } 131 fieldValues[columnName] = value.Interface() 132 } 133 return fieldValues, nil 134 } 135 136 // ColumnNames translates field names to column names. 137 func (e *RegisteredEntity) ColumnNames(fieldNames []string) ([]string, error) { 138 if fieldNames == nil || len(fieldNames) == 0 { 139 for _, field := range e.table.ColToField { 140 fieldNames = append(fieldNames, field) 141 } 142 } 143 columnNames := make([]string, len(fieldNames)) 144 for i, fieldName := range fieldNames { 145 columnName, ok := e.table.FieldToCol[fieldName] 146 if !ok { 147 return nil, errors.Errorf("%s is not a valid field for %s", fieldName, e.table.StructName) 148 } 149 columnNames[i] = columnName 150 } 151 return columnNames, nil 152 } 153 154 // SetFieldValues is a helper for populating a DOSA entity with the given 155 // fieldName->value map 156 func (e *RegisteredEntity) SetFieldValues(entity DomainObject, fieldValues map[string]FieldValue, fieldsToRead []string) { 157 r := reflect.ValueOf(entity).Elem() 158 if fieldsToRead == nil { 159 for columnName := range fieldValues { 160 fieldsToRead = append(fieldsToRead, columnName) 161 } 162 } 163 //for columnName, fieldValue := range fieldValues { 164 for _, columnName := range fieldsToRead { 165 // column name may be different from the entity's field name, so we 166 // have to look it up along the way. 167 fieldName, ok := e.table.ColToField[columnName] 168 if !ok { 169 continue // we ignore fields that we don't know about 170 } 171 fieldValue := fieldValues[columnName] 172 val := r.FieldByName(fieldName) 173 if !val.IsValid() { 174 panic("Field " + fieldName + " is is not a valid field for " + e.table.StructName) 175 } 176 177 var fv reflect.Value 178 if fieldValue != nil { 179 fv = reflect.ValueOf(fieldValue) 180 } 181 if !fv.IsValid() || fv.Kind() == reflect.Ptr && fv.IsNil() { 182 val.Set(reflect.Zero(val.Type())) 183 continue 184 } 185 186 switch val.Type() { 187 case uuidType, boolType, int64Type, stringType, int32Type, doubleType, timestampType, blobType: 188 val.Set(reflect.Indirect(fv)) 189 case nullUUIDType, nullStringType, nullInt32Type, nullInt64Type, nullDoubleType, nullBoolType, nullTimeType: 190 if fv.CanAddr() { 191 val.Set(fv.Addr()) 192 } else { 193 val.Set(fv) 194 } 195 } 196 197 } 198 } 199 200 // Registrar is the interface to register DOSA entities. 201 type Registrar interface { 202 Scope() string 203 NamePrefix() string 204 Find(DomainObject) (*RegisteredEntity, error) 205 FindAll() ([]*RegisteredEntity, error) 206 } 207 208 // prefixedRegistrar puts every entity under a prefix. 209 // This registrar is not threadsafe. However, Register step is done in 210 // bootstrap/client-init phase (usually with a single thread), 211 // and after this phase, multiple goroutines can safely read from this registrar 212 type prefixedRegistrar struct { 213 scope string 214 prefix string 215 fqnIndex map[FQN]*RegisteredEntity 216 typeIndex map[reflect.Type]*RegisteredEntity 217 } 218 219 // NewRegistrar returns a new Registrar for the scope, name prefix and 220 // entities provided. `dosa.Client` implementations are intended to use scope 221 // and prefix to uniquely identify where entities should live but the 222 // registrar itself is only responsible for basic accounting of entities. 223 // DEPRECATED: use (github.com/uber-go/dosa/registry).NewRegistrar instead. 224 func NewRegistrar(scope, prefix string, entities ...DomainObject) (Registrar, error) { 225 baseFQN, err := ToFQN(prefix) 226 if err != nil { 227 return nil, errors.Wrap(err, "failed to construct Registrar") 228 } 229 fqnIndex := make(map[FQN]*RegisteredEntity) 230 typeIndex := make(map[reflect.Type]*RegisteredEntity) 231 232 // index all entities by "FQN" (it's canonical namespace) 233 // and by type. 234 // TODO: when FQN changes, this will need to be updated 235 for _, e := range entities { 236 table, err := TableFromInstance(e) 237 if err != nil { 238 return nil, errors.Wrapf(err, "failed to register entity") 239 } 240 241 // use table name (aka FQN) as an internal lookup key 242 fqn, err := baseFQN.Child(table.Name) 243 if err != nil { 244 // shouldn't happen if TableFromInstance behave correctly 245 return nil, errors.Wrap(err, "failed to register entity, this is most likely a bug in DOSA") 246 } 247 248 // use entity type as internal lookup key 249 typ := reflect.TypeOf(e).Elem() 250 251 // create instance and index it 252 re := NewRegisteredEntity(scope, prefix, table) 253 fqnIndex[fqn] = re 254 typeIndex[typ] = re 255 } 256 257 return &prefixedRegistrar{ 258 scope: scope, 259 prefix: prefix, 260 fqnIndex: fqnIndex, 261 typeIndex: typeIndex, 262 }, nil 263 } 264 265 // Scope returns the registrar's scope. 266 func (r *prefixedRegistrar) Scope() string { 267 return r.scope 268 } 269 270 // NamePrefix returns the registrar's prefix. 271 func (r *prefixedRegistrar) NamePrefix() string { 272 return r.prefix 273 } 274 275 // Find looks at its internal index to find a registration that matches the 276 // entity instance provided. Return an error when not found. 277 func (r *prefixedRegistrar) Find(entity DomainObject) (*RegisteredEntity, error) { 278 t := reflect.TypeOf(entity).Elem() 279 re, ok := r.typeIndex[t] 280 if !ok { 281 return nil, errors.Errorf("failed to find registration for entity %q", t.Name()) 282 } 283 return re, nil 284 } 285 286 // FindAll returns all registered entities from its internal index. 287 func (r *prefixedRegistrar) FindAll() ([]*RegisteredEntity, error) { 288 res := []*RegisteredEntity{} 289 for _, re := range r.fqnIndex { 290 res = append(res, re) 291 } 292 if len(res) == 0 { 293 return nil, errors.Errorf("registry.FindAll returned empty") 294 } 295 return res, nil 296 }