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