github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/shared/datastore_cloud.go (about)

     1  // Copyright 2018 The WPT Dashboard Project. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package shared
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  
    11  	"cloud.google.com/go/datastore"
    12  	"google.golang.org/api/iterator"
    13  )
    14  
    15  type cloudKey struct {
    16  	key *datastore.Key
    17  }
    18  
    19  func (k cloudKey) IntID() int64 {
    20  	return k.key.ID
    21  }
    22  
    23  func (k cloudKey) StringID() string {
    24  	return k.key.Name
    25  }
    26  
    27  func (k cloudKey) Kind() string {
    28  	return k.key.Kind
    29  }
    30  
    31  // NewAppEngineDatastore creates a Datastore implementation, or a Datastore
    32  // implementation with Redis in front to cache all TestRun reads if cached
    33  // is true.
    34  //
    35  // Both variants (cached or not) are backed by Cloud Datastore SDK, using
    36  // Clients initialized at startup in webapp.
    37  func NewAppEngineDatastore(ctx context.Context, cached bool) Datastore {
    38  	ds := cloudDatastore{
    39  		ctx:    ctx,
    40  		client: Clients.datastore,
    41  	}
    42  	if cached {
    43  		return cachedDatastore{ds, ctx}
    44  	}
    45  	return ds
    46  }
    47  
    48  // NewCloudDatastore creates a Datastore implementation that is backed by a
    49  // given Cloud Datastore client.
    50  func NewCloudDatastore(ctx context.Context, client *datastore.Client) Datastore {
    51  	return cloudDatastore{
    52  		ctx:    ctx,
    53  		client: client,
    54  	}
    55  }
    56  
    57  type cloudDatastore struct {
    58  	ctx    context.Context
    59  	client *datastore.Client
    60  }
    61  
    62  func (d cloudDatastore) TestRunQuery() TestRunQuery {
    63  	return testRunQueryImpl{store: d}
    64  }
    65  
    66  func (d cloudDatastore) Context() context.Context {
    67  	return d.ctx
    68  }
    69  
    70  func (d cloudDatastore) Done() interface{} {
    71  	return iterator.Done
    72  }
    73  
    74  func (d cloudDatastore) NewQuery(typeName string) Query {
    75  	return cloudQuery{
    76  		query: datastore.NewQuery(typeName),
    77  	}
    78  }
    79  
    80  func (d cloudDatastore) NewIDKey(typeName string, id int64) Key {
    81  	return cloudKey{
    82  		key: datastore.IDKey(typeName, id, nil),
    83  	}
    84  }
    85  
    86  func (d cloudDatastore) NewIncompleteKey(typeName string) Key {
    87  	return cloudKey{
    88  		key: datastore.IncompleteKey(typeName, nil),
    89  	}
    90  }
    91  
    92  func (d cloudDatastore) ReserveID(typeName string) (Key, error) {
    93  	keys, err := d.client.AllocateIDs(d.ctx, []*datastore.Key{datastore.IncompleteKey(typeName, nil)})
    94  	if err != nil {
    95  		return nil, err
    96  	} else if len(keys) < 1 {
    97  		return nil, errors.New("Failed to create a key")
    98  	}
    99  	return cloudKey{
   100  		key: keys[0],
   101  	}, nil
   102  }
   103  
   104  func (d cloudDatastore) NewNameKey(typeName string, name string) Key {
   105  	return cloudKey{
   106  		key: datastore.NameKey(typeName, name, nil),
   107  	}
   108  }
   109  
   110  func (d cloudDatastore) GetAll(q Query, dst interface{}) ([]Key, error) {
   111  	keys, err := d.client.GetAll(d.ctx, q.(cloudQuery).query, dst)
   112  	cast := make([]Key, len(keys))
   113  	for i := range keys {
   114  		cast[i] = cloudKey{key: keys[i]}
   115  	}
   116  	return cast, err
   117  }
   118  
   119  func (d cloudDatastore) Get(k Key, dst interface{}) error {
   120  	cast := k.(cloudKey).key
   121  	err := d.client.Get(d.ctx, cast, dst)
   122  	if err == datastore.ErrNoSuchEntity {
   123  		return ErrNoSuchEntity
   124  	}
   125  	return err
   126  }
   127  
   128  func (d cloudDatastore) GetMulti(keys []Key, dst interface{}) error {
   129  	cast := make([]*datastore.Key, len(keys))
   130  	for i := range keys {
   131  		cast[i] = keys[i].(cloudKey).key
   132  	}
   133  	err := d.client.GetMulti(d.ctx, cast, dst)
   134  	if multiError, ok := err.(datastore.MultiError); ok {
   135  		errors := make([]error, len(multiError))
   136  		for i, err := range multiError {
   137  			if err == datastore.ErrNoSuchEntity {
   138  				errors[i] = ErrNoSuchEntity
   139  			} else {
   140  				errors[i] = err
   141  			}
   142  		}
   143  		return NewMultiError(errors, "datastore.GetMulti")
   144  	}
   145  	return err
   146  }
   147  
   148  func (d cloudDatastore) Put(key Key, src interface{}) (Key, error) {
   149  	newkey, err := d.client.Put(d.ctx, key.(cloudKey).key, src)
   150  	return cloudKey{newkey}, err
   151  }
   152  
   153  func (d cloudDatastore) PutMulti(keys []Key, src interface{}) ([]Key, error) {
   154  	cast := make([]*datastore.Key, len(keys))
   155  	for i := range keys {
   156  		cast[i] = keys[i].(cloudKey).key
   157  	}
   158  
   159  	srcKeys, err := d.client.PutMulti(d.ctx, cast, src)
   160  	newKeys := make([]Key, len(srcKeys))
   161  	for i := range srcKeys {
   162  		newKeys[i] = cloudKey{srcKeys[i]}
   163  	}
   164  	return newKeys, err
   165  }
   166  
   167  func (d cloudDatastore) Insert(key Key, src interface{}) error {
   168  	_, err := d.client.RunInTransaction(d.ctx, func(txn *datastore.Transaction) error {
   169  		var empty map[string]interface{}
   170  		err := txn.Get(key.(cloudKey).key, &empty)
   171  		if err == nil {
   172  			return ErrEntityAlreadyExists
   173  		} else if err != datastore.ErrNoSuchEntity {
   174  			return err
   175  		}
   176  		_, err = txn.Put(key.(cloudKey).key, src)
   177  		return err
   178  	})
   179  	return err
   180  }
   181  
   182  func (d cloudDatastore) Update(key Key, dst interface{}, mutator func(obj interface{}) error) error {
   183  	_, err := d.client.RunInTransaction(d.ctx, func(txn *datastore.Transaction) error {
   184  		if err := txn.Get(key.(cloudKey).key, dst); err != nil && err != datastore.ErrNoSuchEntity {
   185  			return err
   186  		}
   187  		if err := mutator(dst); err != nil {
   188  			return err
   189  		}
   190  		_, err := txn.Put(key.(cloudKey).key, dst)
   191  		return err
   192  	})
   193  	return err
   194  }
   195  
   196  type cloudQuery struct {
   197  	query *datastore.Query
   198  }
   199  
   200  func (q cloudQuery) Filter(filterStr string, value interface{}) Query {
   201  	return cloudQuery{q.query.Filter(filterStr, value)}
   202  }
   203  
   204  func (q cloudQuery) Project(fields ...string) Query {
   205  	return cloudQuery{q.query.Project(fields...)}
   206  }
   207  
   208  func (q cloudQuery) Offset(offset int) Query {
   209  	return cloudQuery{q.query.Offset(offset)}
   210  }
   211  
   212  func (q cloudQuery) Limit(limit int) Query {
   213  	return cloudQuery{q.query.Limit(limit)}
   214  }
   215  
   216  func (q cloudQuery) Order(order string) Query {
   217  	return cloudQuery{q.query.Order(order)}
   218  }
   219  
   220  func (q cloudQuery) KeysOnly() Query {
   221  	return cloudQuery{q.query.KeysOnly()}
   222  }
   223  
   224  func (q cloudQuery) Distinct() Query {
   225  	return cloudQuery{q.query.Distinct()}
   226  }
   227  
   228  func (q cloudQuery) Run(store Datastore) Iterator {
   229  	cStore := store.(cloudDatastore)
   230  	return cloudIterator{
   231  		iter: cStore.client.Run(cStore.ctx, q.query),
   232  	}
   233  }
   234  
   235  type cloudIterator struct {
   236  	iter *datastore.Iterator
   237  }
   238  
   239  func (i cloudIterator) Next(dst interface{}) (Key, error) {
   240  	key, err := i.iter.Next(dst)
   241  	return cloudKey{key}, err
   242  }