goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/database/timeout_test.go (about) 1 package database 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/samber/lo" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 "gorm.io/driver/sqlite" 14 "gorm.io/gorm" 15 "goyave.dev/goyave/v5/config" 16 ) 17 18 func prepareTimeoutTest(dbName string) *gorm.DB { 19 cfg := config.LoadDefault() 20 cfg.Set("app.debug", false) 21 cfg.Set("database.connection", "sqlite3_timeout_test") 22 cfg.Set("database.name", fmt.Sprintf("timeout_test_%s.db", dbName)) 23 cfg.Set("database.options", "mode=memory") 24 cfg.Set("database.defaultReadQueryTimeout", 5) 25 cfg.Set("database.defaultWriteQueryTimeout", 5) 26 db, err := New(cfg, nil) 27 if err != nil { 28 panic(err) 29 } 30 31 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 32 if err := db.Session(&gorm.Session{NewDB: true, Context: ctx}).AutoMigrate(&TestUser{}); err != nil { 33 cancel() 34 panic(err) 35 } 36 cancel() 37 38 author := userGenerator() 39 if err := db.Create(author).Error; err != nil { 40 panic(err) 41 } 42 return db 43 } 44 45 func TestTimeoutPlugin(t *testing.T) { 46 47 RegisterDialect("sqlite3_timeout_test", "file:{name}?{options}", sqlite.Open) 48 t.Cleanup(func() { 49 mu.Lock() 50 delete(dialects, "sqlite3_timeout_test") 51 mu.Unlock() 52 }) 53 54 t.Run("Callbacks", func(t *testing.T) { 55 db := prepareTimeoutTest(t.Name()) 56 57 callbacks := db.Callback() 58 59 assert.NotNil(t, callbacks.Create().Get(timeoutCallbackBeforeName)) 60 assert.NotNil(t, callbacks.Create().Get(timeoutCallbackAfterName)) 61 62 assert.NotNil(t, callbacks.Query().Get(timeoutCallbackBeforeName)) 63 assert.NotNil(t, callbacks.Query().Get(timeoutCallbackAfterName)) 64 65 assert.NotNil(t, callbacks.Delete().Get(timeoutCallbackBeforeName)) 66 assert.NotNil(t, callbacks.Delete().Get(timeoutCallbackAfterName)) 67 68 assert.NotNil(t, callbacks.Update().Get(timeoutCallbackBeforeName)) 69 assert.NotNil(t, callbacks.Update().Get(timeoutCallbackAfterName)) 70 71 // assert.NotNil(t, callbacks.Row().Get(timeoutCallbackBeforeName)) 72 // assert.NotNil(t, callbacks.Row().Get(timeoutCallbackAfterName)) 73 74 assert.NotNil(t, callbacks.Raw().Get(timeoutCallbackBeforeName)) 75 assert.NotNil(t, callbacks.Raw().Get(timeoutCallbackAfterName)) 76 }) 77 78 t.Run("timeout", func(t *testing.T) { 79 db := prepareTimeoutTest(t.Name()) 80 81 // Generate a huge WHERE condition to artificially make the query very long 82 args := lo.RepeatBy(20000, func(index int) string { 83 return fmt.Sprintf("foobar_%d@example.org", index) 84 }) 85 86 users := []*TestUser{} 87 res := db.Select("*").Where("email IN (?)", args).Find(&users) 88 require.Error(t, res.Error) 89 assert.Equal(t, "context deadline exceeded", res.Error.Error()) 90 }) 91 92 t.Run("re-use_statement", func(t *testing.T) { 93 db := prepareTimeoutTest(t.Name()) 94 95 users := []*TestUser{} 96 db = db.Select("*").Where("email", "johndoe@example.org").Find(&users) 97 require.NoError(t, db.Error) 98 db = db.Select("*").Where("email", "johndoe@example.org").Find(&users) 99 require.NoError(t, db.Error) 100 }) 101 102 t.Run("dont_override_predefined_context", func(t *testing.T) { 103 db := prepareTimeoutTest(t.Name()) 104 105 // Generate a huge WHERE condition to artificially make the query very long 106 args := lo.RepeatBy(20000, func(index int) string { 107 return fmt.Sprintf("foobar_%d@example.org", index) 108 }) 109 110 users := []*TestUser{} 111 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 112 defer cancel() 113 114 // The context is replaced with a longer timeout so the query can be completed. 115 res := db.WithContext(ctx).Select("*").Where("email IN (?)", args).Find(&users) 116 require.NoError(t, res.Error) 117 }) 118 119 t.Run("disabled", func(t *testing.T) { 120 cfg := config.LoadDefault() 121 cfg.Set("app.debug", false) 122 cfg.Set("database.connection", "sqlite3_timeout_test") 123 cfg.Set("database.name", "timeout_test_disabled.db") 124 cfg.Set("database.options", "mode=memory") 125 cfg.Set("database.defaultReadQueryTimeout", 0) 126 cfg.Set("database.defaultWriteQueryTimeout", 0) 127 db, err := New(cfg, nil) 128 if err != nil { 129 panic(err) 130 } 131 132 if err := db.AutoMigrate(&TestUser{}); err != nil { 133 panic(err) 134 } 135 136 // Generate a huge WHERE condition to artificially make the query very long 137 args := lo.RepeatBy(20000, func(index int) string { 138 return fmt.Sprintf("foobar_%d@example.org", index) 139 }) 140 141 users := []*TestUser{} 142 res := db.Select("*").Where("email IN (?)", args).Find(&users) 143 require.NoError(t, res.Error) 144 }) 145 146 t.Run("transaction_many_queries", func(t *testing.T) { 147 t.Cleanup(func() { 148 // The DB is not in memory here 149 if err := os.Remove("timeout_many_queries_test.db"); err != nil { 150 panic(err) 151 } 152 }) 153 cfg := config.LoadDefault() 154 cfg.Set("app.debug", false) 155 cfg.Set("database.connection", "sqlite3_timeout_test") 156 cfg.Set("database.name", "timeout_many_queries_test.db") 157 cfg.Set("database.defaultReadQueryTimeout", 200) 158 cfg.Set("database.defaultWriteQueryTimeout", 200) 159 db, err := New(cfg, nil) 160 if err != nil { 161 panic(err) 162 } 163 164 ctx, cancel := context.WithTimeout(context.Background(), time.Hour) 165 if err := db.WithContext(ctx).AutoMigrate(&TestUser{}); err != nil { 166 panic(err) 167 } 168 defer cancel() 169 170 author := userGenerator() 171 if err := db.Create(author).Error; err != nil { 172 panic(err) 173 } 174 175 // The timeout should be per query 176 // If we execute a lot of queries that take a cumulated time 177 // superior to the configured timeout, we should have no error. 178 179 // Generate a huge WHERE condition to artificially make the query long 180 args := lo.RepeatBy(1000, func(index int) string { 181 return fmt.Sprintf("foobar_%d@example.org", index) 182 }) 183 err = db.Transaction(func(_ *gorm.DB) error { 184 for i := 0; i < 5000; i++ { 185 users := []*TestUser{} 186 res := db.Select("*").Where("email IN (?)", args).Find(&users) 187 if res.Error != nil { 188 return res.Error 189 } 190 } 191 return nil 192 }) 193 require.NoError(t, err) 194 }) 195 }