ariga.io/entcache@v0.1.1-0.20230620164151-0eb723a11c40/driver_test.go (about)

     1  package entcache_test
     2  
     3  import (
     4  	"context"
     5  	"database/sql/driver"
     6  	"testing"
     7  	"time"
     8  
     9  	"ariga.io/entcache"
    10  
    11  	"entgo.io/ent/dialect"
    12  	"entgo.io/ent/dialect/sql"
    13  	"github.com/DATA-DOG/go-sqlmock"
    14  	"github.com/go-redis/redismock/v9"
    15  )
    16  
    17  func TestDriver_ContextLevel(t *testing.T) {
    18  	db, mock, err := sqlmock.New()
    19  	if err != nil {
    20  		t.Fatal(err)
    21  	}
    22  	drv := sql.OpenDB(dialect.MySQL, db)
    23  
    24  	t.Run("One", func(t *testing.T) {
    25  		drv := entcache.NewDriver(drv, entcache.ContextLevel())
    26  		mock.ExpectQuery("SELECT id FROM users").
    27  			WillReturnRows(
    28  				sqlmock.NewRows([]string{"id"}).
    29  					AddRow(1).
    30  					AddRow(2).
    31  					AddRow(3),
    32  			)
    33  		ctx := entcache.NewContext(context.Background())
    34  		expectQuery(ctx, t, drv, "SELECT id FROM users", []interface{}{int64(1), int64(2), int64(3)})
    35  		expectQuery(ctx, t, drv, "SELECT id FROM users", []interface{}{int64(1), int64(2), int64(3)})
    36  		if err := mock.ExpectationsWereMet(); err != nil {
    37  			t.Fatal(err)
    38  		}
    39  	})
    40  
    41  	t.Run("Multi", func(t *testing.T) {
    42  		drv := entcache.NewDriver(drv, entcache.ContextLevel())
    43  		mock.ExpectQuery("SELECT name FROM users").
    44  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
    45  		ctx1 := entcache.NewContext(context.Background())
    46  		expectQuery(ctx1, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
    47  		ctx2 := entcache.NewContext(context.Background())
    48  		mock.ExpectQuery("SELECT name FROM users").
    49  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
    50  		expectQuery(ctx2, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
    51  		if err := mock.ExpectationsWereMet(); err != nil {
    52  			t.Fatal(err)
    53  		}
    54  	})
    55  
    56  	t.Run("TTL", func(t *testing.T) {
    57  		drv := entcache.NewDriver(drv, entcache.ContextLevel(), entcache.TTL(-1))
    58  		mock.ExpectQuery("SELECT name FROM users").
    59  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
    60  		mock.ExpectQuery("SELECT name FROM users").
    61  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
    62  		ctx := entcache.NewContext(context.Background())
    63  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
    64  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
    65  		if err := mock.ExpectationsWereMet(); err != nil {
    66  			t.Fatal(err)
    67  		}
    68  	})
    69  }
    70  
    71  func TestDriver_Levels(t *testing.T) {
    72  	db, mock, err := sqlmock.New()
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	drv := sql.OpenDB(dialect.Postgres, db)
    77  
    78  	t.Run("One", func(t *testing.T) {
    79  		drv := entcache.NewDriver(drv, entcache.TTL(time.Second))
    80  		mock.ExpectQuery("SELECT age FROM users").
    81  			WillReturnRows(
    82  				sqlmock.NewRows([]string{"age"}).
    83  					AddRow(20.1).
    84  					AddRow(30.2).
    85  					AddRow(40.5),
    86  			)
    87  		expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5})
    88  		expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5})
    89  		if err := mock.ExpectationsWereMet(); err != nil {
    90  			t.Fatal(err)
    91  		}
    92  	})
    93  
    94  	t.Run("Multi", func(t *testing.T) {
    95  		drv := entcache.NewDriver(
    96  			drv,
    97  			entcache.Levels(
    98  				entcache.NewLRU(-1), // Nop.
    99  				entcache.NewLRU(0),  // No limit.
   100  			),
   101  		)
   102  		mock.ExpectQuery("SELECT age FROM users").
   103  			WillReturnRows(
   104  				sqlmock.NewRows([]string{"age"}).
   105  					AddRow(20.1).
   106  					AddRow(30.2).
   107  					AddRow(40.5),
   108  			)
   109  		expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5})
   110  		expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5})
   111  		if err := mock.ExpectationsWereMet(); err != nil {
   112  			t.Fatal(err)
   113  		}
   114  	})
   115  
   116  	t.Run("Redis", func(t *testing.T) {
   117  		var (
   118  			rdb, rmock = redismock.NewClientMock()
   119  			drv        = entcache.NewDriver(
   120  				drv,
   121  				entcache.Levels(
   122  					entcache.NewLRU(-1),
   123  					entcache.NewRedis(rdb),
   124  				),
   125  				entcache.Hash(func(string, []interface{}) (entcache.Key, error) {
   126  					return 1, nil
   127  				}),
   128  			)
   129  		)
   130  		mock.ExpectQuery("SELECT active FROM users").
   131  			WillReturnRows(sqlmock.NewRows([]string{"active"}).AddRow(true).AddRow(false))
   132  		rmock.ExpectGet("1").RedisNil()
   133  		buf, _ := entcache.Entry{Values: [][]driver.Value{{true}, {false}}}.MarshalBinary()
   134  		rmock.ExpectSet("1", buf, 0).RedisNil()
   135  		expectQuery(context.Background(), t, drv, "SELECT active FROM users", []interface{}{true, false})
   136  		rmock.ExpectGet("1").SetVal(string(buf))
   137  		expectQuery(context.Background(), t, drv, "SELECT active FROM users", []interface{}{true, false})
   138  		if err := rmock.ExpectationsWereMet(); err != nil {
   139  			t.Fatal(err)
   140  		}
   141  		expected := entcache.Stats{Gets: 2, Hits: 1}
   142  		if s := drv.Stats(); s != expected {
   143  			t.Errorf("unexpected stats: %v != %v", s, expected)
   144  		}
   145  	})
   146  }
   147  
   148  func TestDriver_ContextOptions(t *testing.T) {
   149  	db, mock, err := sqlmock.New()
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	drv := sql.OpenDB(dialect.MySQL, db)
   154  
   155  	t.Run("Skip", func(t *testing.T) {
   156  		drv := entcache.NewDriver(drv)
   157  		mock.ExpectQuery("SELECT name FROM users").
   158  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   159  		ctx := context.Background()
   160  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   161  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   162  		mock.ExpectQuery("SELECT name FROM users").
   163  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   164  		skipCtx := entcache.Skip(ctx)
   165  		expectQuery(skipCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   166  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   167  		if err := mock.ExpectationsWereMet(); err != nil {
   168  			t.Fatal(err)
   169  		}
   170  	})
   171  
   172  	t.Run("Evict", func(t *testing.T) {
   173  		drv := entcache.NewDriver(drv)
   174  		mock.ExpectQuery("SELECT name FROM users").
   175  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   176  		ctx := context.Background()
   177  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   178  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   179  		mock.ExpectQuery("SELECT name FROM users").
   180  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   181  		evictCtx := entcache.Evict(ctx)
   182  		expectQuery(evictCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   183  		mock.ExpectQuery("SELECT name FROM users").
   184  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   185  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   186  		if err := mock.ExpectationsWereMet(); err != nil {
   187  			t.Fatal(err)
   188  		}
   189  	})
   190  
   191  	t.Run("WithTTL", func(t *testing.T) {
   192  		drv := entcache.NewDriver(drv)
   193  		mock.ExpectQuery("SELECT name FROM users").
   194  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   195  		ttlCtx := entcache.WithTTL(context.Background(), -1)
   196  		expectQuery(ttlCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   197  		mock.ExpectQuery("SELECT name FROM users").
   198  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   199  		expectQuery(ttlCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   200  		if err := mock.ExpectationsWereMet(); err != nil {
   201  			t.Fatal(err)
   202  		}
   203  	})
   204  
   205  	t.Run("WithKey", func(t *testing.T) {
   206  		drv := entcache.NewDriver(drv)
   207  		mock.ExpectQuery("SELECT name FROM users").
   208  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   209  		ctx := context.Background()
   210  		keyCtx := entcache.WithKey(ctx, "cache-key")
   211  		expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   212  		expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   213  		mock.ExpectQuery("SELECT name FROM users").
   214  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   215  		expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   216  		if err := drv.Cache.Del(ctx, "cache-key"); err != nil {
   217  			t.Fatal(err)
   218  		}
   219  		mock.ExpectQuery("SELECT name FROM users").
   220  			WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m"))
   221  		expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"})
   222  		if err := mock.ExpectationsWereMet(); err != nil {
   223  			t.Fatal(err)
   224  		}
   225  		expected := entcache.Stats{Gets: 4, Hits: 1}
   226  		if s := drv.Stats(); s != expected {
   227  			t.Errorf("unexpected stats: %v != %v", s, expected)
   228  		}
   229  	})
   230  }
   231  
   232  func TestDriver_SkipInsert(t *testing.T) {
   233  	db, mock, err := sqlmock.New()
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	drv := entcache.NewDriver(sql.OpenDB(dialect.Postgres, db), entcache.Hash(func(string, []interface{}) (entcache.Key, error) {
   238  		t.Fatal("Driver.Query should not be called for INSERT statements")
   239  		return nil, nil
   240  	}))
   241  	mock.ExpectQuery("INSERT INTO users DEFAULT VALUES RETURNING id").
   242  		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
   243  	expectQuery(context.Background(), t, drv, "INSERT INTO users DEFAULT VALUES RETURNING id", []interface{}{int64(1)})
   244  	if err := mock.ExpectationsWereMet(); err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	var expected entcache.Stats
   248  	if s := drv.Stats(); s != expected {
   249  		t.Errorf("unexpected stats: %v != %v", s, expected)
   250  	}
   251  }
   252  
   253  func expectQuery(ctx context.Context, t *testing.T, drv dialect.Driver, query string, args []interface{}) {
   254  	rows := &sql.Rows{}
   255  	if err := drv.Query(ctx, query, []interface{}{}, rows); err != nil {
   256  		t.Fatalf("unexpected query failure: %q: %v", query, err)
   257  	}
   258  	var dest []interface{}
   259  	for rows.Next() {
   260  		var v interface{}
   261  		if err := rows.Scan(&v); err != nil {
   262  			t.Fatal("unexpected Rows.Scan failure:", err)
   263  		}
   264  		dest = append(dest, v)
   265  	}
   266  	if len(dest) != len(args) {
   267  		t.Fatalf("mismatch rows length: %d != %d", len(dest), len(args))
   268  	}
   269  	for i := range dest {
   270  		if dest[i] != args[i] {
   271  			t.Fatalf("mismatch values: %v != %v", dest[i], args[i])
   272  		}
   273  	}
   274  	if err := rows.Close(); err != nil {
   275  		t.Fatal(err)
   276  	}
   277  }