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 }