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  }