github.com/shuguocloud/go-zero@v1.3.0/core/stores/sqlc/cachedsql_test.go (about) 1 package sqlc 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "os" 11 "runtime" 12 "sync" 13 "sync/atomic" 14 "testing" 15 "time" 16 17 "github.com/alicebob/miniredis/v2" 18 "github.com/stretchr/testify/assert" 19 "github.com/shuguocloud/go-zero/core/fx" 20 "github.com/shuguocloud/go-zero/core/logx" 21 "github.com/shuguocloud/go-zero/core/stat" 22 "github.com/shuguocloud/go-zero/core/stores/cache" 23 "github.com/shuguocloud/go-zero/core/stores/redis" 24 "github.com/shuguocloud/go-zero/core/stores/redis/redistest" 25 "github.com/shuguocloud/go-zero/core/stores/sqlx" 26 ) 27 28 func init() { 29 logx.Disable() 30 stat.SetReporter(nil) 31 } 32 33 func TestCachedConn_GetCache(t *testing.T) { 34 resetStats() 35 r, clean, err := redistest.CreateRedis() 36 assert.Nil(t, err) 37 defer clean() 38 39 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) 40 var value string 41 err = c.GetCache("any", &value) 42 assert.Equal(t, ErrNotFound, err) 43 r.Set("any", `"value"`) 44 err = c.GetCache("any", &value) 45 assert.Nil(t, err) 46 assert.Equal(t, "value", value) 47 } 48 49 func TestStat(t *testing.T) { 50 resetStats() 51 r, clean, err := redistest.CreateRedis() 52 assert.Nil(t, err) 53 defer clean() 54 55 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) 56 57 for i := 0; i < 10; i++ { 58 var str string 59 err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 60 *v.(*string) = "zero" 61 return nil 62 }) 63 if err != nil { 64 t.Error(err) 65 } 66 } 67 68 assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total)) 69 assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit)) 70 } 71 72 func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) { 73 resetStats() 74 r, clean, err := redistest.CreateRedis() 75 assert.Nil(t, err) 76 defer clean() 77 78 c := NewConn(dummySqlConn{}, cache.CacheConf{ 79 { 80 RedisConf: redis.RedisConf{ 81 Host: r.Addr, 82 Type: redis.NodeType, 83 }, 84 Weight: 100, 85 }, 86 }, cache.WithExpiry(time.Second*10)) 87 88 var str string 89 err = c.QueryRowIndex(&str, "index", func(s interface{}) string { 90 return fmt.Sprintf("%s/1234", s) 91 }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { 92 *v.(*string) = "zero" 93 return "primary", errors.New("foo") 94 }, func(conn sqlx.SqlConn, v, pri interface{}) error { 95 assert.Equal(t, "primary", pri) 96 *v.(*string) = "xin" 97 return nil 98 }) 99 assert.NotNil(t, err) 100 101 err = c.QueryRowIndex(&str, "index", func(s interface{}) string { 102 return fmt.Sprintf("%s/1234", s) 103 }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { 104 *v.(*string) = "zero" 105 return "primary", nil 106 }, func(conn sqlx.SqlConn, v, pri interface{}) error { 107 assert.Equal(t, "primary", pri) 108 *v.(*string) = "xin" 109 return nil 110 }) 111 assert.Nil(t, err) 112 assert.Equal(t, "zero", str) 113 val, err := r.Get("index") 114 assert.Nil(t, err) 115 assert.Equal(t, `"primary"`, val) 116 val, err = r.Get("primary/1234") 117 assert.Nil(t, err) 118 assert.Equal(t, `"zero"`, val) 119 } 120 121 func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) { 122 resetStats() 123 r, clean, err := redistest.CreateRedis() 124 assert.Nil(t, err) 125 defer clean() 126 127 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10), 128 cache.WithNotFoundExpiry(time.Second)) 129 130 var str string 131 r.Set("index", `"primary"`) 132 err = c.QueryRowIndex(&str, "index", func(s interface{}) string { 133 return fmt.Sprintf("%s/1234", s) 134 }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { 135 assert.Fail(t, "should not go here") 136 return "primary", nil 137 }, func(conn sqlx.SqlConn, v, primary interface{}) error { 138 *v.(*string) = "xin" 139 assert.Equal(t, "primary", primary) 140 return nil 141 }) 142 assert.Nil(t, err) 143 assert.Equal(t, "xin", str) 144 val, err := r.Get("index") 145 assert.Nil(t, err) 146 assert.Equal(t, `"primary"`, val) 147 val, err = r.Get("primary/1234") 148 assert.Nil(t, err) 149 assert.Equal(t, `"xin"`, val) 150 } 151 152 func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) { 153 const ( 154 primaryInt8 int8 = 100 155 primaryInt16 int16 = 10000 156 primaryInt32 int32 = 10000000 157 primaryInt64 int64 = 10000000 158 primaryUint8 uint8 = 100 159 primaryUint16 uint16 = 10000 160 primaryUint32 uint32 = 10000000 161 primaryUint64 uint64 = 10000000 162 ) 163 tests := []struct { 164 name string 165 primary interface{} 166 primaryCache string 167 }{ 168 { 169 name: "int8 primary", 170 primary: primaryInt8, 171 primaryCache: fmt.Sprint(primaryInt8), 172 }, 173 { 174 name: "int16 primary", 175 primary: primaryInt16, 176 primaryCache: fmt.Sprint(primaryInt16), 177 }, 178 { 179 name: "int32 primary", 180 primary: primaryInt32, 181 primaryCache: fmt.Sprint(primaryInt32), 182 }, 183 { 184 name: "int64 primary", 185 primary: primaryInt64, 186 primaryCache: fmt.Sprint(primaryInt64), 187 }, 188 { 189 name: "uint8 primary", 190 primary: primaryUint8, 191 primaryCache: fmt.Sprint(primaryUint8), 192 }, 193 { 194 name: "uint16 primary", 195 primary: primaryUint16, 196 primaryCache: fmt.Sprint(primaryUint16), 197 }, 198 { 199 name: "uint32 primary", 200 primary: primaryUint32, 201 primaryCache: fmt.Sprint(primaryUint32), 202 }, 203 { 204 name: "uint64 primary", 205 primary: primaryUint64, 206 primaryCache: fmt.Sprint(primaryUint64), 207 }, 208 } 209 210 for _, test := range tests { 211 t.Run(test.name, func(t *testing.T) { 212 resetStats() 213 r, clean, err := redistest.CreateRedis() 214 assert.Nil(t, err) 215 defer clean() 216 217 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10), 218 cache.WithNotFoundExpiry(time.Second)) 219 220 var str string 221 r.Set("index", test.primaryCache) 222 err = c.QueryRowIndex(&str, "index", func(s interface{}) string { 223 return fmt.Sprintf("%v/1234", s) 224 }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { 225 assert.Fail(t, "should not go here") 226 return test.primary, nil 227 }, func(conn sqlx.SqlConn, v, primary interface{}) error { 228 *v.(*string) = "xin" 229 assert.Equal(t, primary, primary) 230 return nil 231 }) 232 assert.Nil(t, err) 233 assert.Equal(t, "xin", str) 234 val, err := r.Get("index") 235 assert.Nil(t, err) 236 assert.Equal(t, test.primaryCache, val) 237 val, err = r.Get(test.primaryCache + "/1234") 238 assert.Nil(t, err) 239 assert.Equal(t, `"xin"`, val) 240 }) 241 } 242 } 243 244 func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) { 245 caches := map[string]string{ 246 "index": "primary", 247 "primary/1234": "xin", 248 } 249 250 for k, v := range caches { 251 t.Run(k+"/"+v, func(t *testing.T) { 252 resetStats() 253 r, clean, err := redistest.CreateRedis() 254 assert.Nil(t, err) 255 defer clean() 256 257 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10), 258 cache.WithNotFoundExpiry(time.Second)) 259 260 var str string 261 r.Set(k, v) 262 err = c.QueryRowIndex(&str, "index", func(s interface{}) string { 263 return fmt.Sprintf("%s/1234", s) 264 }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { 265 *v.(*string) = "xin" 266 return "primary", nil 267 }, func(conn sqlx.SqlConn, v, primary interface{}) error { 268 *v.(*string) = "xin" 269 assert.Equal(t, "primary", primary) 270 return nil 271 }) 272 assert.Nil(t, err) 273 assert.Equal(t, "xin", str) 274 val, err := r.Get("index") 275 assert.Nil(t, err) 276 assert.Equal(t, `"primary"`, val) 277 val, err = r.Get("primary/1234") 278 assert.Nil(t, err) 279 assert.Equal(t, `"xin"`, val) 280 }) 281 } 282 } 283 284 func TestStatCacheFails(t *testing.T) { 285 resetStats() 286 log.SetOutput(ioutil.Discard) 287 defer log.SetOutput(os.Stdout) 288 289 r := redis.New("localhost:59999") 290 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) 291 292 for i := 0; i < 20; i++ { 293 var str string 294 err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 295 return errors.New("db failed") 296 }) 297 assert.NotNil(t, err) 298 } 299 300 assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total)) 301 assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit)) 302 assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Miss)) 303 assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.DbFails)) 304 } 305 306 func TestStatDbFails(t *testing.T) { 307 resetStats() 308 r, clean, err := redistest.CreateRedis() 309 assert.Nil(t, err) 310 defer clean() 311 312 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) 313 314 for i := 0; i < 20; i++ { 315 var str string 316 err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 317 return errors.New("db failed") 318 }) 319 assert.NotNil(t, err) 320 } 321 322 assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total)) 323 assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit)) 324 assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.DbFails)) 325 } 326 327 func TestStatFromMemory(t *testing.T) { 328 resetStats() 329 r, clean, err := redistest.CreateRedis() 330 assert.Nil(t, err) 331 defer clean() 332 333 c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) 334 335 var all sync.WaitGroup 336 var wait sync.WaitGroup 337 all.Add(10) 338 wait.Add(4) 339 go func() { 340 var str string 341 err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 342 *v.(*string) = "zero" 343 return nil 344 }) 345 if err != nil { 346 t.Error(err) 347 } 348 wait.Wait() 349 runtime.Gosched() 350 all.Done() 351 }() 352 353 for i := 0; i < 4; i++ { 354 go func() { 355 var str string 356 wait.Done() 357 err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 358 *v.(*string) = "zero" 359 return nil 360 }) 361 if err != nil { 362 t.Error(err) 363 } 364 all.Done() 365 }() 366 } 367 for i := 0; i < 5; i++ { 368 go func() { 369 var str string 370 err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error { 371 *v.(*string) = "zero" 372 return nil 373 }) 374 if err != nil { 375 t.Error(err) 376 } 377 all.Done() 378 }() 379 } 380 all.Wait() 381 382 assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total)) 383 assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit)) 384 } 385 386 func TestCachedConnQueryRow(t *testing.T) { 387 r, clean, err := redistest.CreateRedis() 388 assert.Nil(t, err) 389 defer clean() 390 391 const ( 392 key = "user" 393 value = "any" 394 ) 395 var conn trackedConn 396 var user string 397 var ran bool 398 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) 399 err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { 400 ran = true 401 user = value 402 return nil 403 }) 404 assert.Nil(t, err) 405 actualValue, err := r.Get(key) 406 assert.Nil(t, err) 407 var actual string 408 assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual)) 409 assert.Equal(t, value, actual) 410 assert.Equal(t, value, user) 411 assert.True(t, ran) 412 } 413 414 func TestCachedConnQueryRowFromCache(t *testing.T) { 415 r, clean, err := redistest.CreateRedis() 416 assert.Nil(t, err) 417 defer clean() 418 419 const ( 420 key = "user" 421 value = "any" 422 ) 423 var conn trackedConn 424 var user string 425 var ran bool 426 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) 427 assert.Nil(t, c.SetCache(key, value)) 428 err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { 429 ran = true 430 user = value 431 return nil 432 }) 433 assert.Nil(t, err) 434 actualValue, err := r.Get(key) 435 assert.Nil(t, err) 436 var actual string 437 assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual)) 438 assert.Equal(t, value, actual) 439 assert.Equal(t, value, user) 440 assert.False(t, ran) 441 } 442 443 func TestQueryRowNotFound(t *testing.T) { 444 r, clean, err := redistest.CreateRedis() 445 assert.Nil(t, err) 446 defer clean() 447 448 const key = "user" 449 var conn trackedConn 450 var user string 451 var ran int 452 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) 453 for i := 0; i < 20; i++ { 454 err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { 455 ran++ 456 return sql.ErrNoRows 457 }) 458 assert.Exactly(t, sqlx.ErrNotFound, err) 459 } 460 assert.Equal(t, 1, ran) 461 } 462 463 func TestCachedConnExec(t *testing.T) { 464 r, clean, err := redistest.CreateRedis() 465 assert.Nil(t, err) 466 defer clean() 467 468 var conn trackedConn 469 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) 470 _, err = c.ExecNoCache("delete from user_table where id='kevin'") 471 assert.Nil(t, err) 472 assert.True(t, conn.execValue) 473 } 474 475 func TestCachedConnExecDropCache(t *testing.T) { 476 r, err := miniredis.Run() 477 assert.Nil(t, err) 478 defer fx.DoWithTimeout(func() error { 479 r.Close() 480 return nil 481 }, time.Second) 482 483 const ( 484 key = "user" 485 value = "any" 486 ) 487 var conn trackedConn 488 c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30)) 489 assert.Nil(t, c.SetCache(key, value)) 490 _, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) { 491 return conn.Exec("delete from user_table where id='kevin'") 492 }, key) 493 assert.Nil(t, err) 494 assert.True(t, conn.execValue) 495 _, err = r.Get(key) 496 assert.Exactly(t, miniredis.ErrKeyNotFound, err) 497 _, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) { 498 return nil, errors.New("foo") 499 }, key) 500 assert.NotNil(t, err) 501 } 502 503 func TestCachedConnExecDropCacheFailed(t *testing.T) { 504 const key = "user" 505 var conn trackedConn 506 r := redis.New("anyredis:8888") 507 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) 508 _, err := c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) { 509 return conn.Exec("delete from user_table where id='kevin'") 510 }, key) 511 // async background clean, retry logic 512 assert.Nil(t, err) 513 } 514 515 func TestCachedConnQueryRows(t *testing.T) { 516 r, clean, err := redistest.CreateRedis() 517 assert.Nil(t, err) 518 defer clean() 519 520 var conn trackedConn 521 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) 522 var users []string 523 err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'") 524 assert.Nil(t, err) 525 assert.True(t, conn.queryRowsValue) 526 } 527 528 func TestCachedConnTransact(t *testing.T) { 529 r, clean, err := redistest.CreateRedis() 530 assert.Nil(t, err) 531 defer clean() 532 533 var conn trackedConn 534 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) 535 err = c.Transact(func(session sqlx.Session) error { 536 return nil 537 }) 538 assert.Nil(t, err) 539 assert.True(t, conn.transactValue) 540 } 541 542 func TestQueryRowNoCache(t *testing.T) { 543 r, clean, err := redistest.CreateRedis() 544 assert.Nil(t, err) 545 defer clean() 546 547 const ( 548 key = "user" 549 value = "any" 550 ) 551 var user string 552 var ran bool 553 conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error { 554 user = value 555 ran = true 556 return nil 557 }} 558 c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) 559 err = c.QueryRowNoCache(&user, key) 560 assert.Nil(t, err) 561 assert.Equal(t, value, user) 562 assert.True(t, ran) 563 } 564 565 func TestNewConnWithCache(t *testing.T) { 566 r, clean, err := redistest.CreateRedis() 567 assert.Nil(t, err) 568 defer clean() 569 570 var conn trackedConn 571 c := NewConnWithCache(&conn, cache.NewNode(r, exclusiveCalls, stats, sql.ErrNoRows)) 572 _, err = c.ExecNoCache("delete from user_table where id='kevin'") 573 assert.Nil(t, err) 574 assert.True(t, conn.execValue) 575 } 576 577 func resetStats() { 578 atomic.StoreUint64(&stats.Total, 0) 579 atomic.StoreUint64(&stats.Hit, 0) 580 atomic.StoreUint64(&stats.Miss, 0) 581 atomic.StoreUint64(&stats.DbFails, 0) 582 } 583 584 type dummySqlConn struct { 585 queryRow func(interface{}, string, ...interface{}) error 586 } 587 588 func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) { 589 return nil, nil 590 } 591 592 func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) { 593 return nil, nil 594 } 595 596 func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error { 597 if d.queryRow != nil { 598 return d.queryRow(v, query, args...) 599 } 600 return nil 601 } 602 603 func (d dummySqlConn) QueryRowPartial(v interface{}, query string, args ...interface{}) error { 604 return nil 605 } 606 607 func (d dummySqlConn) QueryRows(v interface{}, query string, args ...interface{}) error { 608 return nil 609 } 610 611 func (d dummySqlConn) QueryRowsPartial(v interface{}, query string, args ...interface{}) error { 612 return nil 613 } 614 615 func (d dummySqlConn) RawDB() (*sql.DB, error) { 616 return nil, nil 617 } 618 619 func (d dummySqlConn) Transact(func(session sqlx.Session) error) error { 620 return nil 621 } 622 623 type trackedConn struct { 624 dummySqlConn 625 execValue bool 626 queryRowsValue bool 627 transactValue bool 628 } 629 630 func (c *trackedConn) Exec(query string, args ...interface{}) (sql.Result, error) { 631 c.execValue = true 632 return c.dummySqlConn.Exec(query, args...) 633 } 634 635 func (c *trackedConn) QueryRows(v interface{}, query string, args ...interface{}) error { 636 c.queryRowsValue = true 637 return c.dummySqlConn.QueryRows(v, query, args...) 638 } 639 640 func (c *trackedConn) RawDB() (*sql.DB, error) { 641 return nil, nil 642 } 643 644 func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error { 645 c.transactValue = true 646 return c.dummySqlConn.Transact(fn) 647 }