github.com/octohelm/storage@v0.0.0-20240516030302-1ac2cc1ea347/pkg/dal/session_test.go (about)

     1  package dal
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  
     8  	"github.com/google/uuid"
     9  	"github.com/octohelm/storage/internal/testutil"
    10  	"github.com/octohelm/storage/pkg/dberr"
    11  	"github.com/octohelm/storage/pkg/sqlbuilder"
    12  	"github.com/octohelm/storage/testdata/model"
    13  )
    14  
    15  type UserParam struct {
    16  	Age []int64 `name:"age" in:"query" `
    17  }
    18  
    19  func (i UserParam) Apply(q Querier) Querier {
    20  	if q.ExistsTable(model.UserT) {
    21  		if len(i.Age) > 0 {
    22  			q = q.WhereAnd(model.UserT.Age.V(sqlbuilder.In(i.Age...)))
    23  		}
    24  	}
    25  
    26  	return q
    27  }
    28  
    29  func TestCRUD(t *testing.T) {
    30  	ctxs := []context.Context{
    31  		ContextWithDatabase(t, "dal_sql_crud", ""),
    32  		ContextWithDatabase(t, "dal_sql_crud", "postgres://postgres@localhost?sslmode=disable"),
    33  	}
    34  
    35  	for i := range ctxs {
    36  		ctx := ctxs[i]
    37  
    38  		t.Run("Save one user", func(t *testing.T) {
    39  			usr := &model.User{
    40  				Name: uuid.New().String(),
    41  				Age:  100,
    42  			}
    43  			err := Prepare(usr).IncludesZero(model.UserT.Nickname).
    44  				Returning(model.UserT.ID).Scan(usr).
    45  				Save(ctx)
    46  
    47  			testutil.Expect(t, err, testutil.Be[error](nil))
    48  			testutil.Expect(t, usr.ID, testutil.Not(testutil.Be(uint64(0))))
    49  
    50  			t.Run("Save same user agent, should conflict", func(t *testing.T) {
    51  				usr2 := &model.User{
    52  					Name: usr.Name,
    53  				}
    54  				err := Prepare(usr2).Save(ctx)
    55  				testutil.Expect(t, dberr.IsErrConflict(err), testutil.Be(true))
    56  			})
    57  
    58  			t.Run("Save same user again, when set ignore should not clause conflict", func(t *testing.T) {
    59  				usr2 := &model.User{
    60  					Name:     usr.Name,
    61  					Nickname: "test",
    62  				}
    63  
    64  				err := Prepare(usr2).
    65  					OnConflict(model.UserT.I.IName).DoNothing().
    66  					Returning(model.UserT.ID, model.UserT.Age).Scan(usr2).
    67  					Save(ctx)
    68  
    69  				testutil.Expect(t, err, testutil.Be[error](nil))
    70  			})
    71  
    72  			t.Run("Save same user again, when set ignore should not clause conflict", func(t *testing.T) {
    73  				usr2 := &model.User{
    74  					Name:     usr.Name,
    75  					Nickname: "test",
    76  				}
    77  
    78  				err := Prepare(usr2).
    79  					OnConflict(model.UserT.I.IName).DoUpdateSet(model.UserT.Nickname).
    80  					Returning(model.UserT.ID, model.UserT.Age, model.UserT.Username).Scan(usr2).
    81  					Save(ctx)
    82  
    83  				testutil.Expect(t, err, testutil.Be[error](nil))
    84  				testutil.Expect(t, usr2.ID, testutil.Be(usr.ID))
    85  				testutil.Expect(t, usr2.Age, testutil.Be(usr.Age))
    86  			})
    87  
    88  			t.Run("Update", func(t *testing.T) {
    89  				usr2 := &model.User{
    90  					Nickname: "test test",
    91  				}
    92  				update := Prepare(usr2).Where(model.UserT.ID.V(sqlbuilder.Eq[uint64](100)))
    93  
    94  				err := update.Save(ctx)
    95  				testutil.Expect(t, err, testutil.Be[error](nil))
    96  			})
    97  
    98  			t.Run("SoftDelete", func(t *testing.T) {
    99  				deletedUser := &model.User{}
   100  				update := Prepare(&model.User{}).ForDelete().
   101  					Returning().Scan(deletedUser).
   102  					Where(model.UserT.ID.V(sqlbuilder.Eq(usr.ID)))
   103  
   104  				err := update.Save(ctx)
   105  				testutil.Expect(t, err, testutil.Be[error](nil))
   106  				testutil.Expect(t, deletedUser.ID, testutil.Be(usr.ID))
   107  				testutil.Expect(t, deletedUser.ID, testutil.Be(usr.ID))
   108  			})
   109  
   110  			t.Run("Delete", func(t *testing.T) {
   111  				deletedUser := &model.User{}
   112  
   113  				update := Prepare(&model.User{}).ForDelete(HardDelete()).
   114  					Returning().Scan(deletedUser).
   115  					Where(model.UserT.ID.V(sqlbuilder.Eq(usr.ID)))
   116  
   117  				err := update.Save(ctx)
   118  				testutil.Expect(t, err, testutil.Be[error](nil))
   119  				testutil.Expect(t, deletedUser.ID, testutil.Be(usr.ID))
   120  			})
   121  		})
   122  
   123  		t.Run("Insert multi Users and Orgs", func(t *testing.T) {
   124  			err := Tx(ctx, &model.Org{}, func(ctx context.Context) error {
   125  				for i := 0; i < 2; i++ {
   126  					org := &model.Org{
   127  						Name: uuid.New().String(),
   128  					}
   129  					if err := Prepare(org).Returning(model.OrgT.ID).Scan(org).Save(ctx); err != nil {
   130  						return err
   131  					}
   132  				}
   133  
   134  				for i := 0; i < 110; i++ {
   135  					usr := &model.User{
   136  						Name: uuid.New().String(),
   137  						Age:  int64(i),
   138  					}
   139  
   140  					err := Prepare(usr).IncludesZero(model.UserT.Nickname).
   141  						Returning(model.UserT.ID).Scan(usr).
   142  						Save(ctx)
   143  					if err != nil {
   144  						return err
   145  					}
   146  
   147  					if i >= 100 {
   148  						if err := Prepare(usr).ForDelete().Where(
   149  							model.UserT.Age.V(sqlbuilder.Eq[int64](usr.Age)),
   150  						).Save(ctx); err != nil {
   151  							return err
   152  						}
   153  					}
   154  
   155  					orgUsr := &model.OrgUser{
   156  						UserID: usr.ID,
   157  						OrgID:  usr.ID%2 + 1,
   158  					}
   159  					if err := Prepare(orgUsr).Save(ctx); err != nil {
   160  						return err
   161  					}
   162  				}
   163  
   164  				return nil
   165  			})
   166  
   167  			testutil.Expect(t, err, testutil.Be[error](nil))
   168  
   169  			t.Run("Then Queries", func(t *testing.T) {
   170  				t.Run("Count", func(t *testing.T) {
   171  					c, err := From(model.UserT).Count(ctx)
   172  
   173  					testutil.Expect(t, err, testutil.Be[error](nil))
   174  					testutil.Expect(t, c, testutil.Be(100))
   175  				})
   176  
   177  				t.Run("List all", func(t *testing.T) {
   178  					users := make([]model.User, 0)
   179  
   180  					err := From(model.UserT).
   181  						Scan(&users).
   182  						Find(ctx)
   183  
   184  					testutil.Expect(t, err, testutil.Be[error](nil))
   185  					testutil.Expect(t, len(users), testutil.Be(100))
   186  				})
   187  
   188  				t.Run("List partial with cancel", func(t *testing.T) {
   189  					users := make([]*model.User, 0)
   190  
   191  					ctx, cancel := context.WithCancel(ctx)
   192  
   193  					err := From(model.UserT).
   194  						Scan(Recv(func(user *model.User) error {
   195  							users = append(users, user)
   196  
   197  							if len(users) >= 10 {
   198  								cancel()
   199  							}
   200  
   201  							return nil
   202  						})).
   203  						Find(ctx)
   204  
   205  					testutil.Expect(t, err, testutil.Be[error](nil))
   206  					testutil.Expect(t, len(users), testutil.Be(10))
   207  				})
   208  
   209  				t.Run("List all", func(t *testing.T) {
   210  					users := make([]model.User, 0)
   211  
   212  					err := From(model.UserT, IncludeAllRecord()).
   213  						Scan(&users).
   214  						Find(ctx)
   215  
   216  					testutil.Expect(t, err, testutil.Be[error](nil))
   217  					testutil.Expect(t, len(users), testutil.Be(110))
   218  				})
   219  
   220  				t.Run("List all limit 10", func(t *testing.T) {
   221  					users := make([]model.User, 0)
   222  
   223  					err := From(model.UserT).
   224  						Limit(10).
   225  						Scan(&users).
   226  						Find(ctx)
   227  
   228  					testutil.Expect(t, err, testutil.Be[error](nil))
   229  					testutil.Expect(t, len(users), testutil.Be(10))
   230  				})
   231  
   232  				t.Run("List all offset limit 10", func(t *testing.T) {
   233  					users := make([]model.User, 0)
   234  
   235  					err := From(model.UserT).
   236  						Offset(10).Limit(10).
   237  						Scan(&users).
   238  						Find(ctx)
   239  
   240  					testutil.Expect(t, err, testutil.Be[error](nil))
   241  					testutil.Expect(t, len(users), testutil.Be(10))
   242  					testutil.Expect(t, users[0].ID > 1, testutil.Be(true))
   243  				})
   244  
   245  				t.Run("List desc order by", func(t *testing.T) {
   246  					users := make([]model.User, 0)
   247  
   248  					err := From(model.UserT).
   249  						OrderBy(sqlbuilder.DescOrder(model.UserT.ID)).
   250  						Offset(10).Limit(10).
   251  						Scan(&users).
   252  						Find(ctx)
   253  
   254  					testutil.Expect(t, err, testutil.Be[error](nil))
   255  					testutil.Expect(t, len(users), testutil.Be(10))
   256  					testutil.Expect(t, users[0].ID > users[1].ID, testutil.Be(true))
   257  				})
   258  
   259  				t.Run("List where", func(t *testing.T) {
   260  					users := make([]model.User, 0)
   261  
   262  					err := From(model.UserT).
   263  						Apply(UserParam{
   264  							Age: []int64{10},
   265  						}).
   266  						Scan(&users).
   267  						Find(ctx)
   268  
   269  					testutil.Expect(t, err, testutil.Be[error](nil))
   270  					testutil.Expect(t, len(users), testutil.Be(1))
   271  				})
   272  
   273  				t.Run("List where with in", func(t *testing.T) {
   274  					orgUsers := make([]model.OrgUser, 0)
   275  
   276  					err := From(model.OrgUserT).
   277  						Where(model.OrgUserT.UserID.V(InSelect(
   278  							model.UserT.ID,
   279  							From(model.UserT).Where(model.UserT.Age.V(sqlbuilder.Eq(int64(10)))),
   280  						))).
   281  						Scan(&orgUsers).
   282  						Find(ctx)
   283  
   284  					testutil.Expect(t, err, testutil.Be[error](nil))
   285  					testutil.Expect(t, len(orgUsers), testutil.Be(1))
   286  				})
   287  
   288  				t.Run("List where join", func(t *testing.T) {
   289  					users := make([]struct {
   290  						model.User
   291  						Org model.Org
   292  					}, 0)
   293  
   294  					err := From(model.UserT).
   295  						Join(model.OrgUserT, model.OrgUserT.UserID.V(sqlbuilder.EqCol(model.UserT.ID))).
   296  						Join(model.OrgT, model.OrgT.ID.V(sqlbuilder.EqCol(model.OrgUserT.OrgID))).
   297  						Where(model.UserT.Age.V(sqlbuilder.Eq(int64(10)))).
   298  						Scan(&users).
   299  						Find(ctx)
   300  
   301  					testutil.Expect(t, err, testutil.Be[error](nil))
   302  					testutil.Expect(t, len(users), testutil.Be(1))
   303  					testutil.Expect(t, users[0].Org.Name, testutil.Not(testutil.Be("")))
   304  				})
   305  			})
   306  		})
   307  	}
   308  }
   309  
   310  func TestMultipleTxLockedWithSqlite(t *testing.T) {
   311  	ctx := ContextWithDatabase(t, "sql_test", "")
   312  
   313  	t.Run("concurrent insert && query", func(t *testing.T) {
   314  		usr2 := &model.User{
   315  			Name:     "test",
   316  			Nickname: "test",
   317  		}
   318  
   319  		wg := &sync.WaitGroup{}
   320  
   321  		for i := 0; i < 2; i++ {
   322  			wg.Add(1)
   323  			go func() {
   324  				defer wg.Done()
   325  
   326  				err := Prepare(usr2).
   327  					OnConflict(model.UserT.I.IName).DoUpdateSet(model.UserT.Nickname).
   328  					Save(ctx)
   329  
   330  				testutil.Expect(t, err, testutil.Be[error](nil))
   331  			}()
   332  		}
   333  
   334  		for i := 0; i < 2; i++ {
   335  			wg.Add(1)
   336  			go func() {
   337  				defer wg.Done()
   338  
   339  				//err := Tx(ctx, usr2, func(ctx context.Context) error {
   340  				//	return Prepare(usr2).
   341  				//		OnConflict(model.UserT.I.IName).DoUpdateSet(model.UserT.Nickname).
   342  				//		Save(ctx)
   343  				//})
   344  				//
   345  				//testutil.Expect(t, err, testutil.Be[error](nil))
   346  			}()
   347  		}
   348  
   349  		for i := 0; i < 4; i++ {
   350  			wg.Add(1)
   351  			go func() {
   352  				defer wg.Done()
   353  
   354  				err := From(model.UserT).
   355  					Scan(Recv(func(v *model.User) error {
   356  						return nil
   357  					})).
   358  					Find(ctx)
   359  				testutil.Expect(t, err, testutil.Be[error](nil))
   360  			}()
   361  		}
   362  
   363  		wg.Wait()
   364  	})
   365  }
   366  
   367  func ContextWithDatabase(t testing.TB, name string, endpoint string) context.Context {
   368  	t.Helper()
   369  	ctx := testutil.NewContext(t)
   370  
   371  	cat := &sqlbuilder.Tables{}
   372  	cat.Add(model.UserT)
   373  	cat.Add(model.OrgT)
   374  	cat.Add(model.OrgUserT)
   375  
   376  	db := &Database{
   377  		Endpoint:      endpoint,
   378  		EnableMigrate: true,
   379  	}
   380  
   381  	db.ApplyCatalog(name, cat)
   382  	db.SetDefaults()
   383  	err := db.Init(ctx)
   384  	testutil.Expect(t, err, testutil.Be[error](nil))
   385  
   386  	ctx = db.InjectContext(ctx)
   387  
   388  	err = db.Run(ctx)
   389  	testutil.Expect(t, err, testutil.Be[error](nil))
   390  
   391  	t.Cleanup(func() {
   392  		a := SessionFor(ctx, name).Adapter()
   393  
   394  		cat.Range(func(table sqlbuilder.Table, idx int) bool {
   395  			_, e := a.Exec(ctx, a.Dialect().DropTable(table))
   396  			testutil.Expect(t, e, testutil.Be[error](nil))
   397  			return true
   398  		})
   399  
   400  		err := a.Close()
   401  		testutil.Expect(t, err, testutil.Be[error](nil))
   402  	})
   403  
   404  	return ctx
   405  }