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 }