go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/datastore_integration_test.go (about)

     1  // Copyright 2020 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 datastore_test
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/common/logging/memlogger"
    23  
    24  	"go.chromium.org/luci/gae/impl/memory"
    25  	"go.chromium.org/luci/gae/service/datastore"
    26  
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	. "go.chromium.org/luci/common/testing/assertions"
    29  )
    30  
    31  type Foo struct {
    32  	_kind string `gae:"$kind,Foo"`
    33  	ID    int64  `gae:"$id"`
    34  
    35  	MultiVals []string `gae:"multi_vals"`
    36  	SingleVal string   `gae:"single_val"`
    37  	Status    bool     `gae:"status"`
    38  }
    39  
    40  func TestRunMulti(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	Convey("RunMulti", t, func() {
    44  		ctx := memory.Use(context.Background())
    45  		ctx = memlogger.Use(ctx)
    46  		datastore.GetTestable(ctx).AutoIndex(true)
    47  		datastore.GetTestable(ctx).Consistent(true)
    48  
    49  		foos := []*Foo{
    50  			{
    51  				ID:        1,
    52  				MultiVals: []string{"m1", "m2"},
    53  				SingleVal: "s1",
    54  				Status:    true,
    55  			},
    56  			{
    57  				ID:        2,
    58  				MultiVals: []string{"m2", "m3"},
    59  				SingleVal: "s1",
    60  				Status:    false,
    61  			},
    62  			{
    63  				ID:        3,
    64  				MultiVals: []string{"m3", "m4"},
    65  				SingleVal: "s2",
    66  				Status:    true,
    67  			},
    68  			{
    69  				ID:        4,
    70  				MultiVals: []string{"m4", "m5"},
    71  				SingleVal: "s2",
    72  				Status:    false,
    73  			},
    74  		}
    75  
    76  		So(datastore.Put(ctx, foos), ShouldBeNil)
    77  
    78  		Convey("ok", func() {
    79  			Convey("default ordering", func() {
    80  				queries := []*datastore.Query{
    81  					datastore.NewQuery("Foo").Gte("__key__", datastore.KeyForObj(ctx, &Foo{ID: 1})),
    82  				}
    83  				var res []*Foo
    84  				err := datastore.RunMulti(ctx, queries, func(foo *Foo) error {
    85  					res = append(res, foo)
    86  					return nil
    87  				})
    88  				So(err, ShouldBeNil)
    89  				So(res, ShouldResemble, foos)
    90  			})
    91  
    92  			Convey("querying key only", func() {
    93  				queries := []*datastore.Query{
    94  					datastore.NewQuery("Foo").Eq("multi_vals", "m2"),
    95  					datastore.NewQuery("Foo").Eq("multi_vals", "m3"),
    96  				}
    97  				var keys []*datastore.Key
    98  				err := datastore.RunMulti(ctx, queries, func(k *datastore.Key) error {
    99  					keys = append(keys, k)
   100  					return nil
   101  				})
   102  				So(err, ShouldBeNil)
   103  				So(keys, ShouldResemble, []*datastore.Key{
   104  					datastore.KeyForObj(ctx, foos[0]),
   105  					datastore.KeyForObj(ctx, foos[1]),
   106  					datastore.KeyForObj(ctx, foos[2]),
   107  				})
   108  			})
   109  
   110  			Convey("overlapped results with non-default orders", func() {
   111  				queries := []*datastore.Query{
   112  					datastore.NewQuery("Foo").Eq("multi_vals", "m2").Order("-single_val", "status"),
   113  					datastore.NewQuery("Foo").Eq("multi_vals", "m3").Order("-single_val", "status"),
   114  				}
   115  				var res []*Foo
   116  				err := datastore.RunMulti(ctx, queries, func(foo *Foo) error {
   117  					res = append(res, foo)
   118  					return nil
   119  				})
   120  				So(err, ShouldBeNil)
   121  				So(res, ShouldResemble, []*Foo{foos[2], foos[1], foos[0]})
   122  			})
   123  
   124  			Convey("send Stop in cb", func() {
   125  				queries := []*datastore.Query{
   126  					datastore.NewQuery("Foo").Eq("multi_vals", "m2"),
   127  					datastore.NewQuery("Foo").Eq("multi_vals", "m3"),
   128  				}
   129  				var res []*Foo
   130  				err := datastore.RunMulti(ctx, queries, func(foo *Foo) error {
   131  					if len(res) == 2 {
   132  						return datastore.Stop
   133  					}
   134  					res = append(res, foo)
   135  					return nil
   136  				})
   137  				So(err, ShouldBeNil)
   138  				So(res, ShouldResemble, []*Foo{foos[0], foos[1]})
   139  			})
   140  
   141  			Convey("not found", func() {
   142  				queries := []*datastore.Query{
   143  					datastore.NewQuery("Foo").Eq("single_val", "non-existent"),
   144  				}
   145  				var res []*Foo
   146  				err := datastore.RunMulti(ctx, queries, func(foo *Foo) error {
   147  					res = append(res, foo)
   148  					return nil
   149  				})
   150  				So(err, ShouldBeNil)
   151  				So(res, ShouldBeNil)
   152  			})
   153  			Convey("cb with cursorCB", func() {
   154  				queries := []*datastore.Query{
   155  					datastore.NewQuery("Foo").Eq("single_val", "s1"),
   156  					datastore.NewQuery("Foo").Eq("single_val", "s2"),
   157  				}
   158  				var cur datastore.Cursor
   159  				var err error
   160  				var fooses []*Foo
   161  				err = datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   162  					fooses = append(fooses, foo)
   163  					if len(fooses) == 1 {
   164  						cur, err = c()
   165  						if err != nil {
   166  							return err
   167  						}
   168  						return datastore.Stop
   169  					}
   170  					return nil
   171  				})
   172  				So(err, ShouldBeNil)
   173  				So(fooses, ShouldNotBeNil)
   174  				So(fooses, ShouldResemble, []*Foo{foos[0]})
   175  				// Apply the cursor to the queries
   176  				queries, err = datastore.ApplyCursors(ctx, queries, cur)
   177  				So(err, ShouldBeNil)
   178  				So(queries, ShouldNotBeNil)
   179  				err = datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   180  					fooses = append(fooses, foo)
   181  					if len(fooses) == 3 {
   182  						cur, err = c()
   183  						if err != nil {
   184  							return err
   185  						}
   186  						return datastore.Stop
   187  					}
   188  					return nil
   189  				})
   190  				So(err, ShouldBeNil)
   191  				So(fooses, ShouldNotBeNil)
   192  				So(fooses, ShouldResemble, []*Foo{foos[0], foos[1], foos[2]})
   193  				// Apply the cursor to the queries
   194  				queries, err = datastore.ApplyCursors(ctx, queries, cur)
   195  				So(err, ShouldBeNil)
   196  				So(queries, ShouldNotBeNil)
   197  				err = datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   198  					fooses = append(fooses, foo)
   199  					return nil
   200  				})
   201  				So(err, ShouldBeNil)
   202  				So(fooses, ShouldNotBeNil)
   203  				So(fooses, ShouldResemble, []*Foo{foos[0], foos[1], foos[2], foos[3]})
   204  			})
   205  			Convey("cb with cursorCB, repeat entities", func() {
   206  				queries := []*datastore.Query{
   207  					datastore.NewQuery("Foo").Eq("multi_vals", "m2"),
   208  					datastore.NewQuery("Foo").Eq("multi_vals", "m3"),
   209  					datastore.NewQuery("Foo").Eq("multi_vals", "m4"),
   210  				}
   211  				var cur datastore.Cursor
   212  				var err error
   213  				var fooses []*Foo
   214  				err = datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   215  					fooses = append(fooses, foo)
   216  					if len(fooses) == 2 {
   217  						cur, err = c()
   218  						if err != nil {
   219  							return err
   220  						}
   221  						return datastore.Stop
   222  					}
   223  					return nil
   224  				})
   225  				So(err, ShouldBeNil)
   226  				So(fooses, ShouldNotBeNil)
   227  				So(fooses, ShouldResemble, []*Foo{foos[0], foos[1]})
   228  				// Apply the cursor to the queries
   229  				queries, err = datastore.ApplyCursors(ctx, queries, cur)
   230  				So(err, ShouldBeNil)
   231  				So(queries, ShouldNotBeNil)
   232  				err = datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   233  					fooses = append(fooses, foo)
   234  					return nil
   235  				})
   236  				So(err, ShouldBeNil)
   237  				So(fooses, ShouldNotBeNil)
   238  				// RunMulti returns only the unique entities for that run. If there q1 and q2 are in q,
   239  				// which is a slice of query. And if x is a valid response to both. If the callback reads
   240  				// one of the x and then stops and retrieves the cursor. It is possible to repeat the same
   241  				// set of queries with the cursor applied and get x again. This happens because RunMulti
   242  				// doesn't have any knowledge of the previous call and it doesn't see that x has already
   243  				// been returned in a previous run.
   244  				So(fooses, ShouldResemble, []*Foo{foos[0], foos[1], foos[1], foos[2], foos[3]})
   245  			})
   246  		})
   247  
   248  		Convey("bad", func() {
   249  			Convey("Queries with more than one kind", func() {
   250  				queries := []*datastore.Query{
   251  					datastore.NewQuery("Foo"),
   252  					datastore.NewQuery("Foo1"),
   253  				}
   254  
   255  				err := datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   256  					return nil
   257  				})
   258  				So(err, ShouldErrLike, "should query the same kind")
   259  			})
   260  
   261  			Convey("Queries with different order", func() {
   262  				queries := []*datastore.Query{
   263  					datastore.NewQuery("Foo").Order("field1"),
   264  					datastore.NewQuery("Foo").Order("-field1"),
   265  				}
   266  
   267  				err := datastore.RunMulti(ctx, queries, func(foo *Foo, c datastore.CursorCB) error {
   268  					return nil
   269  				})
   270  				So(err, ShouldErrLike, "should use the same order")
   271  			})
   272  		})
   273  
   274  		Convey("context cancelation", func() {
   275  			ctx, cancel := context.WithCancel(ctx)
   276  
   277  			queries := []*datastore.Query{
   278  				datastore.NewQuery("Foo").Eq("multi_vals", "m2"),
   279  				datastore.NewQuery("Foo").Eq("multi_vals", "m3"),
   280  			}
   281  
   282  			err := datastore.RunMulti(ctx, queries, func(k *datastore.Key) error {
   283  				cancel()
   284  				<-ctx.Done() // make sure it "propagates" everywhere
   285  				return nil
   286  			})
   287  			So(err, ShouldEqual, context.Canceled)
   288  		})
   289  
   290  		Convey("callback error", func() {
   291  			customErr := errors.New("boo")
   292  
   293  			queries := []*datastore.Query{
   294  				datastore.NewQuery("Foo").Eq("multi_vals", "m2"),
   295  				datastore.NewQuery("Foo").Eq("multi_vals", "m3"),
   296  			}
   297  
   298  			var keys []*datastore.Key
   299  			err := datastore.RunMulti(ctx, queries, func(k *datastore.Key) error {
   300  				keys = append(keys, k)
   301  				return customErr
   302  			})
   303  			So(err, ShouldEqual, customErr)
   304  			So(keys, ShouldHaveLength, 1)
   305  		})
   306  	})
   307  }