go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/prod/raw_datastore.go (about) 1 // Copyright 2015 The LUCI Authors. 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 prod 16 17 import ( 18 "context" 19 20 "google.golang.org/appengine" 21 "google.golang.org/appengine/datastore" 22 23 "go.chromium.org/luci/common/errors" 24 25 "go.chromium.org/luci/gae/impl/prod/constraints" 26 ds "go.chromium.org/luci/gae/service/datastore" 27 ) 28 29 // useRDS adds a gae.RawDatastore implementation to context, accessible 30 // by gae.GetDS(c) 31 func useRDS(c context.Context) context.Context { 32 return ds.SetRawFactory(c, func(ci context.Context) ds.RawInterface { 33 rds := rdsImpl{ 34 userCtx: ci, 35 ps: getProdState(ci), 36 } 37 rds.aeCtx = rds.ps.context(ci) 38 return &rds 39 }) 40 } 41 42 ////////// Datastore 43 44 type rdsImpl struct { 45 // userCtx is the context that has the luci/gae services and user objects in 46 // it. 47 userCtx context.Context 48 49 // aeCtx is the AppEngine Context that will be used in method calls. This is 50 // derived from ps. 51 aeCtx context.Context 52 53 // ps is the current production state. 54 ps prodState 55 } 56 57 func fixMultiError(err error) error { 58 if err == nil { 59 return nil 60 } 61 if baseME, ok := err.(appengine.MultiError); ok { 62 return errors.NewMultiError(baseME...) 63 } 64 return err 65 } 66 67 func idxCallbacker(err error, amt int, cb func(idx int, err error)) error { 68 if err == nil { 69 for i := 0; i < amt; i++ { 70 cb(i, nil) 71 } 72 return nil 73 } 74 err = fixMultiError(err) 75 me, ok := err.(errors.MultiError) 76 if ok { 77 for i, err := range me { 78 cb(i, err) 79 } 80 return nil 81 } 82 return err 83 } 84 85 func (d *rdsImpl) AllocateIDs(keys []*ds.Key, cb ds.NewKeyCB) error { 86 // Map keys by entity type. 87 entityMap := make(map[string][]int) 88 for i, key := range keys { 89 ks := key.String() 90 entityMap[ks] = append(entityMap[ks], i) 91 } 92 93 // Allocate a set of IDs for each unique entity type. 94 errors := errors.NewLazyMultiError(len(keys)) 95 setErrs := func(idxs []int, err error) { 96 for _, idx := range idxs { 97 errors.Assign(idx, err) 98 } 99 } 100 101 for _, idxs := range entityMap { 102 incomplete := keys[idxs[0]] 103 par, err := dsF2R(d.aeCtx, incomplete.Parent()) 104 if err != nil { 105 setErrs(idxs, err) 106 continue 107 } 108 109 start, _, err := datastore.AllocateIDs(d.aeCtx, incomplete.Kind(), par, len(idxs)) 110 if err != nil { 111 setErrs(idxs, err) 112 continue 113 } 114 115 for i, idx := range idxs { 116 keys[idx] = incomplete.WithID("", start+int64(i)) 117 } 118 } 119 120 for i, key := range keys { 121 if err := errors.GetOne(i); err != nil { 122 cb(i, nil, err) 123 } else { 124 cb(i, key, nil) 125 } 126 } 127 return nil 128 } 129 130 func (d *rdsImpl) DeleteMulti(ks []*ds.Key, cb ds.DeleteMultiCB) error { 131 keys, err := dsMF2R(d.aeCtx, ks) 132 if err == nil { 133 err = datastore.DeleteMulti(d.aeCtx, keys) 134 } 135 return idxCallbacker(err, len(ks), func(idx int, err error) { 136 cb(idx, err) 137 }) 138 } 139 140 func (d *rdsImpl) GetMulti(keys []*ds.Key, _meta ds.MultiMetaGetter, cb ds.GetMultiCB) error { 141 vals := make([]datastore.PropertyLoadSaver, len(keys)) 142 rkeys, err := dsMF2R(d.aeCtx, keys) 143 if err == nil { 144 for i := range keys { 145 vals[i] = &typeFilter{d.aeCtx, ds.PropertyMap{}} 146 } 147 err = datastore.GetMulti(d.aeCtx, rkeys, vals) 148 } 149 return idxCallbacker(err, len(keys), func(idx int, err error) { 150 if pls := vals[idx]; pls != nil { 151 cb(idx, pls.(*typeFilter).pm, err) 152 return 153 } 154 cb(idx, nil, err) 155 }) 156 } 157 158 func (d *rdsImpl) PutMulti(keys []*ds.Key, vals []ds.PropertyMap, cb ds.NewKeyCB) error { 159 rkeys, err := dsMF2R(d.aeCtx, keys) 160 if err == nil { 161 rvals := make([]datastore.PropertyLoadSaver, len(vals)) 162 for i, val := range vals { 163 rvals[i] = &typeFilter{d.aeCtx, val} 164 } 165 rkeys, err = datastore.PutMulti(d.aeCtx, rkeys, rvals) 166 } 167 return idxCallbacker(err, len(keys), func(idx int, err error) { 168 k := (*ds.Key)(nil) 169 if err == nil { 170 k = dsR2F(rkeys[idx]) 171 } 172 cb(idx, k, err) 173 }) 174 } 175 176 func (d *rdsImpl) fixQuery(fq *ds.FinalizedQuery) (*datastore.Query, error) { 177 ret := datastore.NewQuery(fq.Kind()) 178 179 if len(fq.InFilters()) > 0 { 180 return nil, errors.New("IN filters are not supported by GAEv1 datastore implementation") 181 } 182 183 start, end := fq.Bounds() 184 if start != nil { 185 ret = ret.Start(start.(datastore.Cursor)) 186 } 187 if end != nil { 188 ret = ret.End(end.(datastore.Cursor)) 189 } 190 191 for prop, vals := range fq.EqFilters() { 192 if prop == "__ancestor__" { 193 p, err := dsF2RProp(d.aeCtx, vals[0]) 194 if err != nil { 195 return nil, err 196 } 197 ret = ret.Ancestor(p.Value.(*datastore.Key)) 198 } else { 199 filt := prop + "=" 200 for _, v := range vals { 201 p, err := dsF2RProp(d.aeCtx, v) 202 if err != nil { 203 return nil, err 204 } 205 206 // Filter doesn't like ByteString, even though ByteString is the indexed 207 // counterpart of []byte... sigh. 208 if ds, ok := p.Value.(datastore.ByteString); ok { 209 p.Value = []byte(ds) 210 } 211 ret = ret.Filter(filt, p.Value) 212 } 213 } 214 } 215 216 if lnam, lop, lprop := fq.IneqFilterLow(); lnam != "" { 217 p, err := dsF2RProp(d.aeCtx, lprop) 218 if err != nil { 219 return nil, err 220 } 221 ret = ret.Filter(lnam+" "+lop, p.Value) 222 } 223 224 if hnam, hop, hprop := fq.IneqFilterHigh(); hnam != "" { 225 p, err := dsF2RProp(d.aeCtx, hprop) 226 if err != nil { 227 return nil, err 228 } 229 ret = ret.Filter(hnam+" "+hop, p.Value) 230 } 231 232 if fq.EventuallyConsistent() { 233 ret = ret.EventualConsistency() 234 } 235 236 if fq.KeysOnly() { 237 ret = ret.KeysOnly() 238 } 239 240 if lim, ok := fq.Limit(); ok { 241 ret = ret.Limit(int(lim)) 242 } 243 244 if off, ok := fq.Offset(); ok { 245 ret = ret.Offset(int(off)) 246 } 247 248 for _, o := range fq.Orders() { 249 ret = ret.Order(o.String()) 250 } 251 252 ret = ret.Project(fq.Project()...) 253 if fq.Distinct() { 254 ret = ret.Distinct() 255 } 256 257 return ret, nil 258 } 259 260 func (d *rdsImpl) DecodeCursor(s string) (ds.Cursor, error) { 261 return datastore.DecodeCursor(s) 262 } 263 264 func (d *rdsImpl) Run(fq *ds.FinalizedQuery, cb ds.RawRunCB) error { 265 q, err := d.fixQuery(fq) 266 if err != nil { 267 return err 268 } 269 270 t := q.Run(d.aeCtx) 271 272 cfunc := func() (ds.Cursor, error) { 273 return t.Cursor() 274 } 275 tf := typeFilter{} 276 for { 277 k, err := t.Next(&tf) 278 if err == datastore.Done { 279 return nil 280 } 281 if err != nil { 282 return err 283 } 284 if err := cb(dsR2F(k), tf.pm, cfunc); err != nil { 285 return err 286 } 287 } 288 } 289 290 func (d *rdsImpl) Count(fq *ds.FinalizedQuery) (int64, error) { 291 q, err := d.fixQuery(fq) 292 if err != nil { 293 return 0, err 294 } 295 ret, err := q.Count(d.aeCtx) 296 return int64(ret), err 297 } 298 299 func (d *rdsImpl) RunInTransaction(f func(c context.Context) error, opts *ds.TransactionOptions) error { 300 ropts := &datastore.TransactionOptions{ 301 // Cloud Datastore no longer exposes the ability to explicitly allow 302 // cross-group transactions. Since appengine datastore is effectively 303 // deprecated in favor of Cloud Datastore (e.g. the only way to access 304 // Datastore in "current" GAE versions), we just set all transactions as 305 // XG=true now. We believe that this should have no observable difference 306 // for code which only touches a single entity group. The only difference 307 // will be that transactions which would error (due to touching multiple 308 // groups) will now no longer error out. 309 XG: true, 310 } 311 if opts != nil { 312 ropts.Attempts = opts.Attempts 313 ropts.ReadOnly = opts.ReadOnly 314 } 315 return datastore.RunInTransaction(d.aeCtx, func(c context.Context) error { 316 // Derive a prodState with this transaction Context. 317 ps := d.ps 318 ps.ctx = c 319 ps.inTxn = true 320 321 c = withProdState(d.userCtx, ps) 322 return f(c) 323 }, ropts) 324 } 325 326 func (d *rdsImpl) WithoutTransaction() context.Context { 327 c := d.userCtx 328 if d.ps.inTxn { 329 // We're in a transaction. Reset to non-transactional state. 330 ps := d.ps 331 ps.ctx = ps.noTxnCtx 332 ps.inTxn = false 333 c = withProdState(c, ps) 334 } 335 return c 336 } 337 338 func (d *rdsImpl) CurrentTransaction() ds.Transaction { 339 if d.ps.inTxn { 340 // Since we don't distinguish between transactions (yet), we just need this 341 // to be non-nil. 342 return struct{}{} 343 } 344 return nil 345 } 346 347 func (d *rdsImpl) Constraints() ds.Constraints { return constraints.DS() } 348 349 func (d *rdsImpl) GetTestable() ds.Testable { 350 return nil 351 }