blob: ee89b205476799d5a568e549f465d96dc7bd0b46 [file] [log] [blame]
/*
* Copyright (c) 2012 The Goon Authors
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package goon
import (
"fmt"
"reflect"
"google.golang.org/appengine/datastore"
)
// Count returns the number of results for the query.
func (g *Goon) Count(q *datastore.Query) (int, error) {
return q.Count(g.Context)
}
// GetAll runs the query and returns all the keys that match the query, as well
// as appending the values to dst, setting the goon key fields of dst, and
// caching the returned data in local memory.
//
// For "keys-only" queries dst can be nil, however if it is not, then GetAll
// appends zero value structs to dst, only setting the goon key fields.
// No data is cached with "keys-only" queries.
//
// See: https://developers.google.com/appengine/docs/go/datastore/reference#Query.GetAll
func (g *Goon) GetAll(q *datastore.Query, dst interface{}) ([]*datastore.Key, error) {
v := reflect.ValueOf(dst)
dstV := reflect.Indirect(v)
vLenBefore := 0
if dst != nil {
if v.Kind() != reflect.Ptr {
return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
}
v = v.Elem()
if v.Kind() != reflect.Slice {
return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
}
vLenBefore = v.Len()
}
var propLists []datastore.PropertyList
keys, err := q.GetAll(g.Context, &propLists)
if err != nil {
g.error(err)
return keys, err
}
if dst == nil || len(keys) == 0 {
return keys, err
}
keysOnly := (len(propLists) != len(keys))
updateCache := !g.inTransaction && !keysOnly
elemType := v.Type().Elem()
elemTypeIsPtr := false
if elemType.Kind() == reflect.Ptr {
elemType = elemType.Elem()
elemTypeIsPtr = true
}
if elemType.Kind() != reflect.Struct {
return keys, fmt.Errorf("goon: Expected struct, got instead: %v", elemType.Kind())
}
initMem := false
finalLen := vLenBefore + len(keys)
if v.Cap() < finalLen {
// If the slice doesn't have enough capacity for the final length,
// then create a new slice with the exact capacity needed,
// with all elements zero-value initialized already.
newSlice := reflect.MakeSlice(v.Type(), finalLen, finalLen)
if copied := reflect.Copy(newSlice, v); copied != vLenBefore {
return keys, fmt.Errorf("goon: Wanted to copy %v elements to dst but managed %v", vLenBefore, copied)
}
v = newSlice
} else {
// If the slice already has enough capacity ..
if elemTypeIsPtr {
// .. then just change the length if it's a slice of pointers,
// because we will overwrite all the pointers anyway.
v.SetLen(finalLen)
} else {
// .. we need to initialize the memory of every non-pointer element.
initMem = true
}
}
var toCache []*cacheItem
if updateCache {
toCache = make([]*cacheItem, 0, len(keys))
}
var rerr error
for i, k := range keys {
if elemTypeIsPtr {
ev := reflect.New(elemType)
v.Index(vLenBefore + i).Set(ev)
} else if initMem {
ev := reflect.New(elemType).Elem()
v = reflect.Append(v, ev)
}
vi := v.Index(vLenBefore + i)
var e interface{}
if vi.Kind() == reflect.Ptr {
e = vi.Interface()
} else {
e = vi.Addr().Interface()
}
if !keysOnly {
if err := deserializeProperties(e, propLists[i]); err != nil {
if errFieldMismatch(err) {
// If we're not configured to ignore, set rerr to err,
// but proceed with deserializing other entities
if !IgnoreFieldMismatch {
rerr = err
}
} else {
return nil, err
}
}
}
if err := g.setStructKey(e, k); err != nil {
return nil, err
}
if updateCache {
// Serialize the properties
data, err := serializeProperties(propLists[i], true)
if err != nil {
g.error(err)
return nil, err
}
// Prepare the properties for caching
toCache = append(toCache, &cacheItem{key: MemcacheKey(k), value: data})
}
}
if len(toCache) > 0 {
g.cache.SetMulti(toCache)
}
// Set dst to the slice we created
dstV.Set(v)
return keys, rerr
}
// Run runs the query.
func (g *Goon) Run(q *datastore.Query) *Iterator {
return &Iterator{
g: g,
i: q.Run(g.Context),
}
}
// Iterator is the result of running a query.
type Iterator struct {
g *Goon
i *datastore.Iterator
}
// Cursor returns a cursor for the iterator's current location.
func (t *Iterator) Cursor() (datastore.Cursor, error) {
return t.i.Cursor()
}
// Next returns the entity of the next result. When there are no more results,
// datastore.Done is returned as the error.
//
// If the query is not keys only and dst is non-nil, it also loads the entity
// stored for that key into the struct pointer dst, with the same semantics
// and possible errors as for the Get function. This result is cached in memory.
//
// If the query is keys only and dst is non-nil, dst will be given the right id.
//
// Refer to appengine/datastore.Iterator.Next:
// https://developers.google.com/appengine/docs/go/datastore/reference#Iterator.Next
func (t *Iterator) Next(dst interface{}) (*datastore.Key, error) {
var props datastore.PropertyList
k, err := t.i.Next(&props)
if err != nil {
return k, err
}
var rerr error
if dst != nil {
keysOnly := (props == nil)
updateCache := !t.g.inTransaction && !keysOnly
if !keysOnly {
if err := deserializeProperties(dst, props); err != nil {
if errFieldMismatch(err) {
// If we're not configured to ignore, set rerr to err,
// but proceed with work
if !IgnoreFieldMismatch {
rerr = err
}
} else {
return k, err
}
}
}
if err := t.g.setStructKey(dst, k); err != nil {
return k, err
}
if updateCache {
data, err := serializeProperties(props, true)
if err != nil {
return k, err
}
t.g.cache.Set(&cacheItem{key: MemcacheKey(k), value: data})
}
}
return k, rerr
}