go.mercari.io/datastore@v1.8.2/save.go (about)

     1  // Copyright 2014 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datastore
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"time"
    23  )
    24  
    25  type saveOpts struct {
    26  	noIndex   bool
    27  	flatten   bool
    28  	omitEmpty bool
    29  }
    30  
    31  // saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
    32  func saveEntity(ctx context.Context, key Key, src interface{}) (*Entity, error) {
    33  	var err error
    34  	var props []Property
    35  	if e, ok := src.(PropertyLoadSaver); ok {
    36  		props, err = e.Save(ctx)
    37  	} else {
    38  		props, err = SaveStruct(ctx, src)
    39  	}
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	entity, err := propertiesToProtoFake(ctx, key, props)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	return entity, nil
    49  }
    50  
    51  // TODO(djd): Convert this and below to return ([]Property, error).
    52  func saveStructProperty(ctx context.Context, props *[]Property, name string, opts saveOpts, v reflect.Value) error {
    53  	p := Property{
    54  		Name:    name,
    55  		NoIndex: opts.noIndex,
    56  	}
    57  
    58  	if opts.omitEmpty && isEmptyValue(v) {
    59  		return nil
    60  	}
    61  
    62  	// First check if field type implements PLS. If so, use PLS to
    63  	// save.
    64  	ok, err := plsFieldSave(ctx, props, p, name, opts, v)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	if ok {
    69  		return nil
    70  	}
    71  
    72  	if v.Type().AssignableTo(typeOfKey) {
    73  		p.Value = v.Interface()
    74  
    75  	} else {
    76  		// PropertyTranslator returns nil often (e.g. datastore.Key(nil))
    77  		var allowNil bool
    78  		switch x := v.Interface().(type) {
    79  		case time.Time, GeoPoint:
    80  			p.Value = x
    81  		case PropertyTranslator:
    82  			// this case is peculiar to mercari/datastore.
    83  			v, err := x.ToPropertyValue(ctx)
    84  			if err != nil {
    85  				return err
    86  			}
    87  			p.Value = v
    88  			allowNil = true
    89  		default:
    90  			switch v.Kind() {
    91  			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    92  				p.Value = v.Int()
    93  			case reflect.Bool:
    94  				p.Value = v.Bool()
    95  			case reflect.String:
    96  				p.Value = v.String()
    97  			case reflect.Float32, reflect.Float64:
    98  				p.Value = v.Float()
    99  			case reflect.Slice:
   100  				if v.Type().Elem().Kind() == reflect.Uint8 {
   101  					p.Value = v.Bytes()
   102  				} else {
   103  					return saveSliceProperty(ctx, props, name, opts, v)
   104  				}
   105  			case reflect.Ptr:
   106  				if isValidPointerType(v.Type().Elem()) {
   107  					if v.IsNil() {
   108  						// Nil pointer becomes a nil property value (unless omitempty, handled above).
   109  						p.Value = nil
   110  						*props = append(*props, p)
   111  						return nil
   112  					}
   113  					// When we recurse on the derefenced pointer, omitempty no longer applies:
   114  					// we already know the pointer is not empty, it doesn't matter if its referent
   115  					// is empty or not.
   116  					opts.omitEmpty = false
   117  					return saveStructProperty(ctx, props, name, opts, v.Elem())
   118  				}
   119  				if v.Type().Elem().Kind() != reflect.Struct {
   120  					return fmt.Errorf("datastore: unsupported struct field type: %s", v.Type())
   121  				}
   122  				// Pointer to struct is a special case.
   123  				if v.IsNil() {
   124  					return nil
   125  				}
   126  				v = v.Elem()
   127  				fallthrough
   128  			case reflect.Struct:
   129  				if !v.CanAddr() {
   130  					return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
   131  				}
   132  				vi := v.Addr().Interface()
   133  
   134  				sub, err := newStructPLS(vi)
   135  				if err != nil {
   136  					return fmt.Errorf("datastore: unsupported struct field: %v", err)
   137  				}
   138  
   139  				if opts.flatten {
   140  					return sub.save(ctx, props, opts, name+".")
   141  				}
   142  
   143  				var subProps []Property
   144  				err = sub.save(ctx, &subProps, opts, "")
   145  				if err != nil {
   146  					return err
   147  				}
   148  				subKey, err := sub.key(v)
   149  				if err != nil {
   150  					return err
   151  				}
   152  
   153  				p.Value = &Entity{
   154  					Key:        subKey,
   155  					Properties: subProps,
   156  				}
   157  			}
   158  		}
   159  		if !allowNil && p.Value == nil {
   160  			return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
   161  		}
   162  	}
   163  
   164  	*props = append(*props, p)
   165  	return nil
   166  }
   167  
   168  // plsFieldSave first tries to converts v's value to a PLS, then v's addressed
   169  // value to a PLS. If neither succeeds, plsFieldSave returns false for first return
   170  // value.
   171  // If v is successfully converted to a PLS, plsFieldSave will then add the
   172  // Value to property p by way of the PLS's Save method, and append it to props.
   173  //
   174  // If the flatten option is present in opts, name must be prepended to each property's
   175  // name before it is appended to props. Eg. if name were "A" and a subproperty's name
   176  // were "B", the resultant name of the property to be appended to props would be "A.B".
   177  func plsFieldSave(ctx context.Context, props *[]Property, p Property, name string, opts saveOpts, v reflect.Value) (ok bool, err error) {
   178  	vpls, err := plsForSave(v)
   179  	if err != nil {
   180  		return false, err
   181  	}
   182  
   183  	if vpls == nil {
   184  		return false, nil
   185  	}
   186  
   187  	subProps, err := vpls.Save(ctx)
   188  	if err != nil {
   189  		return true, err
   190  	}
   191  
   192  	if opts.flatten {
   193  		for _, subp := range subProps {
   194  			subp.Name = name + "." + subp.Name
   195  			*props = append(*props, subp)
   196  		}
   197  		return true, nil
   198  	}
   199  
   200  	p.Value = &Entity{Properties: subProps}
   201  	*props = append(*props, p)
   202  
   203  	return true, nil
   204  }
   205  
   206  // key extracts the Key interface field from struct v based on the structCodec of s.
   207  func (s structPLS) key(v reflect.Value) (Key, error) {
   208  	if v.Kind() != reflect.Struct {
   209  		return nil, errors.New("datastore: cannot save key of non-struct type")
   210  	}
   211  
   212  	keyField := s.codec.Match(keyFieldName)
   213  
   214  	if keyField == nil {
   215  		return nil, nil
   216  	}
   217  
   218  	f := v.FieldByIndex(keyField.Index)
   219  	k, ok := f.Interface().(Key)
   220  	if !ok {
   221  		return nil, fmt.Errorf("datastore: %s field on struct %T is not a datastore.Key", keyFieldName, v.Interface())
   222  	}
   223  
   224  	return k, nil
   225  }
   226  
   227  func saveSliceProperty(ctx context.Context, props *[]Property, name string, opts saveOpts, v reflect.Value) error {
   228  	// Easy case: if the slice is empty, we're done.
   229  	if v.Len() == 0 {
   230  		return nil
   231  	}
   232  	// Work out the properties generated by the first element in the slice. This will
   233  	// usually be a single property, but will be more if this is a slice of structs.
   234  	var headProps []Property
   235  	if err := saveStructProperty(ctx, &headProps, name, opts, v.Index(0)); err != nil {
   236  		return err
   237  	}
   238  
   239  	// Convert the first element's properties into slice properties, and
   240  	// keep track of the values in a map.
   241  	values := make(map[string][]interface{}, len(headProps))
   242  	for _, p := range headProps {
   243  		values[p.Name] = append(make([]interface{}, 0, v.Len()), p.Value)
   244  	}
   245  
   246  	// Find the elements for the subsequent elements.
   247  	for i := 1; i < v.Len(); i++ {
   248  		elemProps := make([]Property, 0, len(headProps))
   249  		if err := saveStructProperty(ctx, &elemProps, name, opts, v.Index(i)); err != nil {
   250  			return err
   251  		}
   252  		for _, p := range elemProps {
   253  			v, ok := values[p.Name]
   254  			if !ok {
   255  				return fmt.Errorf("datastore: unexpected property %q in elem %d of slice", p.Name, i)
   256  			}
   257  			values[p.Name] = append(v, p.Value)
   258  		}
   259  	}
   260  
   261  	// Convert to the final properties.
   262  	for _, p := range headProps {
   263  		p.Value = values[p.Name]
   264  		*props = append(*props, p)
   265  	}
   266  	return nil
   267  }
   268  
   269  func (s structPLS) Save(ctx context.Context) ([]Property, error) {
   270  	var props []Property
   271  	if err := s.save(ctx, &props, saveOpts{}, ""); err != nil {
   272  		return nil, err
   273  	}
   274  	return props, nil
   275  }
   276  
   277  func (s structPLS) save(ctx context.Context, props *[]Property, opts saveOpts, prefix string) error {
   278  	for _, f := range s.codec {
   279  		name := prefix + f.Name
   280  		v := getField(s.v, f.Index)
   281  		if !v.IsValid() || !v.CanSet() {
   282  			continue
   283  		}
   284  
   285  		var tagOpts saveOpts
   286  		if f.ParsedTag != nil {
   287  			tagOpts = f.ParsedTag.(saveOpts)
   288  		}
   289  
   290  		var opts1 saveOpts
   291  		opts1.noIndex = opts.noIndex || tagOpts.noIndex
   292  		opts1.flatten = opts.flatten || tagOpts.flatten
   293  		opts1.omitEmpty = tagOpts.omitEmpty // don't propagate
   294  		if err := saveStructProperty(ctx, props, name, opts1, v); err != nil {
   295  			return err
   296  		}
   297  	}
   298  	return nil
   299  }
   300  
   301  // getField returns the field from v at the given index path.
   302  // If it encounters a nil-valued field in the path, getField
   303  // stops and returns a zero-valued reflect.Value, preventing the
   304  // panic that would have been caused by reflect's FieldByIndex.
   305  func getField(v reflect.Value, index []int) reflect.Value {
   306  	var zero reflect.Value
   307  	if v.Type().Kind() != reflect.Struct {
   308  		return zero
   309  	}
   310  
   311  	for _, i := range index {
   312  		if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
   313  			if v.IsNil() {
   314  				return zero
   315  			}
   316  			v = v.Elem()
   317  		}
   318  		v = v.Field(i)
   319  	}
   320  	return v
   321  }
   322  
   323  func propertiesToProtoFake(ctx context.Context, key Key, props []Property) (*Entity, error) {
   324  	e := &Entity{
   325  		Key:        key,
   326  		Properties: props,
   327  	}
   328  	for idx, p := range props {
   329  		// Do not send a Key value a a field to datastore.
   330  		if p.Name == keyFieldName {
   331  			continue
   332  		}
   333  
   334  		val, err := interfaceToProtoFake(ctx, p.Value, p.NoIndex)
   335  		if err != nil {
   336  			return nil, fmt.Errorf("datastore: %v for a Property with Name %q", err, p.Name)
   337  		}
   338  		props[idx].Value = val
   339  	}
   340  	return e, nil
   341  }
   342  
   343  func interfaceToProtoFake(ctx context.Context, iv interface{}, noIndex bool) (interface{}, error) {
   344  	switch v := iv.(type) {
   345  	case time.Time:
   346  		if v.Before(minTime) || v.After(maxTime) {
   347  			return nil, errors.New("time value out of range")
   348  		}
   349  		// This rounding process reproduces the cloud.google.com/go/datastore
   350  		// don't use original time.Time's locale and UTC both. use machine default.
   351  		um := toUnixMicro(v)
   352  		return fromUnixMicro(um), nil
   353  	default:
   354  		return v, nil
   355  	}
   356  }
   357  
   358  // isEmptyValue is taken from the encoding/json package in the
   359  // standard library.
   360  func isEmptyValue(v reflect.Value) bool {
   361  	switch v.Kind() {
   362  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   363  		return v.Len() == 0
   364  	case reflect.Bool:
   365  		return !v.Bool()
   366  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   367  		return v.Int() == 0
   368  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   369  		return v.Uint() == 0
   370  	case reflect.Float32, reflect.Float64:
   371  		return v.Float() == 0
   372  	case reflect.Interface, reflect.Ptr:
   373  		return v.IsNil()
   374  	case reflect.Struct:
   375  		if t, ok := v.Interface().(time.Time); ok {
   376  			return t.IsZero()
   377  		}
   378  	}
   379  	return false
   380  }
   381  
   382  // isValidPointerType reports whether a struct field can be a pointer to type t
   383  // for the purposes of saving and loading.
   384  func isValidPointerType(t reflect.Type) bool {
   385  	if t == typeOfTime || t == typeOfGeoPoint {
   386  		return true
   387  	}
   388  	switch t.Kind() {
   389  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   390  		return true
   391  	case reflect.Bool:
   392  		return true
   393  	case reflect.String:
   394  		return true
   395  	case reflect.Float32, reflect.Float64:
   396  		return true
   397  	}
   398  	return false
   399  }