github.com/mjibson/goon@v1.1.0/query.go (about)

     1  /*
     2   * Copyright (c) 2012 The Goon Authors
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package goon
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  
    23  	"google.golang.org/appengine/datastore"
    24  )
    25  
    26  // Count returns the number of results for the query.
    27  func (g *Goon) Count(q *datastore.Query) (int, error) {
    28  	return q.Count(g.Context)
    29  }
    30  
    31  // GetAll runs the query and returns all the keys that match the query, as well
    32  // as appending the values to dst, setting the goon key fields of dst, and
    33  // caching the returned data in local memory.
    34  //
    35  // For "keys-only" queries dst can be nil, however if it is not, then GetAll
    36  // appends zero value structs to dst, only setting the goon key fields.
    37  //
    38  // No data is cached with projection or "keys-only" queries.
    39  //
    40  // See: https://developers.google.com/appengine/docs/go/datastore/reference#Query.GetAll
    41  func (g *Goon) GetAll(q *datastore.Query, dst interface{}) ([]*datastore.Key, error) {
    42  	v := reflect.ValueOf(dst)
    43  	dstV := reflect.Indirect(v)
    44  	vLenBefore := 0
    45  	if dst != nil {
    46  		if v.Kind() != reflect.Ptr {
    47  			return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
    48  		}
    49  		v = v.Elem()
    50  		if v.Kind() != reflect.Slice {
    51  			return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
    52  		}
    53  		vLenBefore = v.Len()
    54  	}
    55  
    56  	var propLists []datastore.PropertyList
    57  	keys, err := q.GetAll(g.Context, &propLists)
    58  	if err != nil {
    59  		g.error(err)
    60  		return keys, err
    61  	}
    62  	if dst == nil || len(keys) == 0 {
    63  		return keys, err
    64  	}
    65  
    66  	projection := false
    67  	if len(propLists) > 0 {
    68  		for i := range propLists {
    69  			if len(propLists[i]) > 0 {
    70  				projection = isIndexValue(&propLists[i][0])
    71  				break
    72  			}
    73  		}
    74  	}
    75  	keysOnly := (len(propLists) != len(keys))
    76  	updateCache := !g.inTransaction && !keysOnly && !projection
    77  
    78  	elemType := v.Type().Elem()
    79  	elemTypeIsPtr := false
    80  	if elemType.Kind() == reflect.Ptr {
    81  		elemType = elemType.Elem()
    82  		elemTypeIsPtr = true
    83  	}
    84  	if elemType.Kind() != reflect.Struct {
    85  		return keys, fmt.Errorf("goon: Expected struct, got instead: %v", elemType.Kind())
    86  	}
    87  
    88  	initMem := false
    89  	finalLen := vLenBefore + len(keys)
    90  	if v.Cap() < finalLen {
    91  		// If the slice doesn't have enough capacity for the final length,
    92  		// then create a new slice with the exact capacity needed,
    93  		// with all elements zero-value initialized already.
    94  		newSlice := reflect.MakeSlice(v.Type(), finalLen, finalLen)
    95  		if copied := reflect.Copy(newSlice, v); copied != vLenBefore {
    96  			return keys, fmt.Errorf("goon: Wanted to copy %v elements to dst but managed %v", vLenBefore, copied)
    97  		}
    98  		v = newSlice
    99  	} else {
   100  		// If the slice already has enough capacity ..
   101  		if elemTypeIsPtr {
   102  			// .. then just change the length if it's a slice of pointers,
   103  			// because we will overwrite all the pointers anyway.
   104  			v.SetLen(finalLen)
   105  		} else {
   106  			// .. we need to initialize the memory of every non-pointer element.
   107  			initMem = true
   108  		}
   109  	}
   110  
   111  	var toCache []*cacheItem
   112  	if updateCache {
   113  		toCache = make([]*cacheItem, 0, len(keys))
   114  	}
   115  	var rerr error
   116  
   117  	for i, k := range keys {
   118  		if elemTypeIsPtr {
   119  			ev := reflect.New(elemType)
   120  			v.Index(vLenBefore + i).Set(ev)
   121  		} else if initMem {
   122  			ev := reflect.New(elemType).Elem()
   123  			v = reflect.Append(v, ev)
   124  		}
   125  		vi := v.Index(vLenBefore + i)
   126  
   127  		var e interface{}
   128  		if vi.Kind() == reflect.Ptr {
   129  			e = vi.Interface()
   130  		} else {
   131  			e = vi.Addr().Interface()
   132  		}
   133  
   134  		if !keysOnly {
   135  			if err := deserializeProperties(e, propLists[i]); err != nil {
   136  				if errFieldMismatch(err) {
   137  					// If we're not configured to ignore, set rerr to err,
   138  					// but proceed with deserializing other entities
   139  					if !IgnoreFieldMismatch {
   140  						rerr = err
   141  					}
   142  				} else {
   143  					return nil, err
   144  				}
   145  			}
   146  		}
   147  
   148  		if err := g.setStructKey(e, k); err != nil {
   149  			return nil, err
   150  		}
   151  
   152  		if updateCache {
   153  			// Serialize the properties
   154  			data, err := serializeProperties(propLists[i], true)
   155  			if err != nil {
   156  				g.error(err)
   157  				return nil, err
   158  			}
   159  			// Prepare the properties for caching
   160  			toCache = append(toCache, &cacheItem{key: cacheKey(k), value: data})
   161  		}
   162  	}
   163  
   164  	if len(toCache) > 0 {
   165  		g.cache.SetMulti(toCache)
   166  	}
   167  
   168  	// Set dst to the slice we created
   169  	dstV.Set(v)
   170  
   171  	return keys, rerr
   172  }
   173  
   174  // Run runs the query.
   175  func (g *Goon) Run(q *datastore.Query) *Iterator {
   176  	return &Iterator{
   177  		g: g,
   178  		i: q.Run(g.Context),
   179  	}
   180  }
   181  
   182  // Iterator is the result of running a query.
   183  type Iterator struct {
   184  	g *Goon
   185  	i *datastore.Iterator
   186  }
   187  
   188  // Cursor returns a cursor for the iterator's current location.
   189  func (t *Iterator) Cursor() (datastore.Cursor, error) {
   190  	return t.i.Cursor()
   191  }
   192  
   193  // Next returns the entity of the next result. When there are no more results,
   194  // datastore.Done is returned as the error.
   195  //
   196  // If the query is not keys only and dst is non-nil, it also loads the entity
   197  // stored for that key into the struct pointer dst, with the same semantics
   198  // and possible errors as for the Get function.
   199  //
   200  // This result is cached in memory, unless it's a projection query.
   201  //
   202  // If the query is keys only and dst is non-nil, dst will be given the right id.
   203  //
   204  // Refer to appengine/datastore.Iterator.Next:
   205  // https://developers.google.com/appengine/docs/go/datastore/reference#Iterator.Next
   206  func (t *Iterator) Next(dst interface{}) (*datastore.Key, error) {
   207  	var props datastore.PropertyList
   208  	k, err := t.i.Next(&props)
   209  	if err != nil {
   210  		return k, err
   211  	}
   212  	var rerr error
   213  	if dst != nil {
   214  		projection := false
   215  		if len(props) > 0 {
   216  			projection = isIndexValue(&props[0])
   217  		}
   218  		keysOnly := (props == nil)
   219  		updateCache := !t.g.inTransaction && !keysOnly && !projection
   220  		if !keysOnly {
   221  			if err := deserializeProperties(dst, props); err != nil {
   222  				if errFieldMismatch(err) {
   223  					// If we're not configured to ignore, set rerr to err,
   224  					// but proceed with work
   225  					if !IgnoreFieldMismatch {
   226  						rerr = err
   227  					}
   228  				} else {
   229  					return k, err
   230  				}
   231  			}
   232  		}
   233  		if err := t.g.setStructKey(dst, k); err != nil {
   234  			return k, err
   235  		}
   236  		if updateCache {
   237  			data, err := serializeProperties(props, true)
   238  			if err != nil {
   239  				return k, err
   240  			}
   241  			t.g.cache.Set(&cacheItem{key: cacheKey(k), value: data})
   242  		}
   243  	}
   244  	return k, rerr
   245  }