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  }