github.com/flower-corp/rosedb@v1.1.2-0.20230117132829-21dc4f7b319a/hash_test.go (about)

     1  package rosedb
     2  
     3  import (
     4  	"errors"
     5  	"github.com/stretchr/testify/assert"
     6  	"math"
     7  	"path/filepath"
     8  	"testing"
     9  )
    10  
    11  func TestRoseDB_HSet(t *testing.T) {
    12  	t.Run("fileio", func(t *testing.T) {
    13  		testRoseDBHSet(t, FileIO, KeyOnlyMemMode)
    14  	})
    15  	t.Run("mmap", func(t *testing.T) {
    16  		testRoseDBHSet(t, MMap, KeyValueMemMode)
    17  	})
    18  	t.Run("mmap-key-only", func(t *testing.T) {
    19  		testRoseDBHSet(t, MMap, KeyOnlyMemMode)
    20  	})
    21  }
    22  
    23  func testRoseDBHSet(t *testing.T, ioType IOType, mode DataIndexMode) {
    24  	path := filepath.Join("/tmp", "rosedb")
    25  	opts := DefaultOptions(path)
    26  	opts.IoType = ioType
    27  	opts.IndexMode = mode
    28  	db, err := Open(opts)
    29  	assert.Nil(t, err)
    30  	defer destroyDB(db)
    31  
    32  	type args struct {
    33  		key []byte
    34  		arg [][]byte
    35  	}
    36  	tests := []struct {
    37  		name    string
    38  		db      *RoseDB
    39  		args    args
    40  		wantErr bool
    41  	}{
    42  		{
    43  			"nil-key-and-field", db, args{key: nil, arg: [][]byte{nil, []byte("val-0")}}, false,
    44  		},
    45  		{
    46  			"wrong-num-of-args", db, args{key: GetKey(2), arg: [][]byte{[]byte("field-0")}}, true,
    47  		},
    48  		{
    49  			"normal-single-pair", db, args{key: GetKey(3), arg: [][]byte{[]byte("field-0"), []byte("val-0")}}, false,
    50  		},
    51  		{
    52  			"normal-mulit-pair", db, args{key: GetKey(4), arg: [][]byte{[]byte("field-0"), []byte("val-0"),
    53  				[]byte("field-1"), []byte("val-1")}}, false,
    54  		},
    55  	}
    56  
    57  	for _, tt := range tests {
    58  		t.Run(tt.name, func(t *testing.T) {
    59  			err := tt.db.HSet(tt.args.key, tt.args.arg...)
    60  			if (err != nil) != tt.wantErr {
    61  				t.Errorf("HSet() error = %v, wantErr %v", err, tt.wantErr)
    62  			}
    63  			if tt.wantErr && !errors.Is(err, ErrWrongNumberOfArgs) {
    64  				t.Errorf("HSet() error = %v, expected error = %v", err, ErrWrongNumberOfArgs)
    65  			}
    66  		})
    67  	}
    68  
    69  	t.Run("check-nil-key-and-field", func(t *testing.T) {
    70  		val, err := db.HGet(nil, nil)
    71  		assert.Nil(t, err)
    72  		assert.Equal(t, []byte(nil), val)
    73  	})
    74  
    75  	t.Run("check-single-field", func(t *testing.T) {
    76  		val, err := db.HGet(GetKey(3), []byte("field-0"))
    77  		assert.Nil(t, err)
    78  		assert.Equal(t, []byte("val-0"), val, "single field not same")
    79  	})
    80  
    81  	t.Run("check-mulit-field", func(t *testing.T) {
    82  		value, err := db.HMGet(GetKey(4), []byte("field-0"), []byte("field-1"))
    83  		assert.Nil(t, err)
    84  		assert.Equal(t, [][]byte{[]byte("val-0"), []byte("val-1")}, value, "multi field not same")
    85  	})
    86  }
    87  
    88  func TestRoseDB_HSetNX(t *testing.T) {
    89  	t.Run("fileio", func(t *testing.T) {
    90  		testRoseDBHSetNX(t, FileIO, KeyOnlyMemMode)
    91  	})
    92  	t.Run("mmap", func(t *testing.T) {
    93  		testRoseDBHSetNX(t, MMap, KeyValueMemMode)
    94  	})
    95  }
    96  
    97  func testRoseDBHSetNX(t *testing.T, ioType IOType, mode DataIndexMode) {
    98  	path := filepath.Join("/tmp", "rosedb")
    99  	opts := DefaultOptions(path)
   100  	opts.IoType = ioType
   101  	opts.IndexMode = mode
   102  	db, err := Open(opts)
   103  	assert.Nil(t, err)
   104  	defer destroyDB(db)
   105  
   106  	_ = db.HSet([]byte("key-1"), []byte("field-1"), []byte("value-1"))
   107  	_ = db.HSet([]byte("key-1"), []byte("field-2"), []byte("value-2"))
   108  	testCases := []struct {
   109  		name   string
   110  		db     *RoseDB
   111  		key    []byte
   112  		field  []byte
   113  		value  []byte
   114  		expRes bool
   115  		expErr error
   116  	}{
   117  		{
   118  			name:   "Non-exist key",
   119  			db:     db,
   120  			key:    []byte("key-2"),
   121  			field:  []byte("field-2"),
   122  			value:  []byte("value-2"),
   123  			expRes: true,
   124  			expErr: nil,
   125  		},
   126  		{
   127  			name:   "Exist key",
   128  			db:     db,
   129  			key:    []byte("key-1"),
   130  			field:  []byte("field-3"),
   131  			value:  []byte("value-3"),
   132  			expRes: true,
   133  			expErr: nil,
   134  		},
   135  		{
   136  			name:   "Non-exist field",
   137  			db:     db,
   138  			key:    []byte("key-1"),
   139  			field:  []byte("field-4"),
   140  			value:  []byte("value-4"),
   141  			expRes: true,
   142  			expErr: nil,
   143  		},
   144  		{
   145  			name:   "Exist field",
   146  			db:     db,
   147  			key:    []byte("key-1"),
   148  			field:  []byte("field-2"),
   149  			value:  []byte("value-3"),
   150  			expRes: false,
   151  			expErr: nil,
   152  		},
   153  	}
   154  
   155  	for _, tc := range testCases {
   156  		t.Run(tc.name, func(t *testing.T) {
   157  			ok, err := tc.db.HSetNX(tc.key, tc.field, tc.value)
   158  			assert.Equal(t, tc.expErr, err)
   159  			assert.Equal(t, tc.expRes, ok)
   160  		})
   161  	}
   162  }
   163  
   164  func TestRoseDB_HGet(t *testing.T) {
   165  	t.Run("fileio", func(t *testing.T) {
   166  		testRoseDBHGet(t, FileIO, KeyOnlyMemMode)
   167  	})
   168  	t.Run("mmap", func(t *testing.T) {
   169  		testRoseDBHGet(t, MMap, KeyValueMemMode)
   170  	})
   171  }
   172  
   173  func testRoseDBHGet(t *testing.T, ioType IOType, mode DataIndexMode) {
   174  	path := filepath.Join("/tmp", "rosedb")
   175  	opts := DefaultOptions(path)
   176  	opts.IoType = ioType
   177  	opts.IndexMode = mode
   178  	db, err := Open(opts)
   179  	assert.Nil(t, err)
   180  	defer destroyDB(db)
   181  
   182  	setKey := []byte("my_set")
   183  	err = db.HSet(setKey, GetKey(1), GetKey(111))
   184  	assert.Nil(t, err)
   185  	v1, err := db.HGet(setKey, GetKey(1))
   186  	assert.Nil(t, err)
   187  	assert.Equal(t, GetKey(111), v1)
   188  
   189  	err = db.HSet(setKey, GetKey(1), GetKey(222))
   190  	assert.Nil(t, err)
   191  
   192  	v2, err := db.HGet(setKey, GetKey(1))
   193  	assert.Nil(t, err)
   194  	assert.Equal(t, GetKey(222), v2)
   195  }
   196  
   197  func TestRoseDB_HMGet(t *testing.T) {
   198  	t.Run("fileio", func(t *testing.T) {
   199  		testRoseDBHMGet(t, FileIO, KeyOnlyMemMode)
   200  	})
   201  	t.Run("mmap", func(t *testing.T) {
   202  		testRoseDBHMGet(t, MMap, KeyValueMemMode)
   203  	})
   204  }
   205  
   206  func testRoseDBHMGet(t *testing.T, ioType IOType, mode DataIndexMode) {
   207  	path := filepath.Join("/tmp", "rosedb")
   208  	opts := DefaultOptions(path)
   209  	opts.IoType = ioType
   210  	opts.IndexMode = mode
   211  	db, err := Open(opts)
   212  	assert.Nil(t, err)
   213  	defer destroyDB(db)
   214  
   215  	setKey := []byte("my_set")
   216  	err = db.HSet(setKey, GetKey(1), GetKey(111))
   217  	assert.Nil(t, err)
   218  
   219  	v1, err := db.HMGet(setKey, GetKey(1))
   220  	assert.Nil(t, err)
   221  	assert.Equal(t, [][]byte{GetKey(111)}, v1)
   222  	//--------------------------------------------------------------------------
   223  	// newe cases
   224  	//--------------------------------------------------------------------------
   225  	key := []byte("my_hash")
   226  
   227  	db.HSet(key, []byte("a"), []byte("hash_data_01"))
   228  	db.HSet(key, []byte("b"), []byte("hash_data_02"))
   229  	db.HSet(key, []byte("c"), []byte("hash_data_03"))
   230  
   231  	type args struct {
   232  		key   []byte
   233  		field [][]byte
   234  	}
   235  
   236  	tests := []struct {
   237  		name    string
   238  		args    args
   239  		wantLen int
   240  		want    [][]byte
   241  		wantErr bool
   242  	}{
   243  		{
   244  			"nil", args{key: key, field: nil}, 0, nil, false,
   245  		},
   246  		{
   247  			"not-exist-key", args{key: []byte("not-exist"), field: [][]byte{[]byte("a"), []byte("b")}}, 2, [][]byte{nil, nil}, false,
   248  		},
   249  		{
   250  			"not-exist-field", args{key: key, field: [][]byte{[]byte("e")}}, 1, [][]byte{nil}, false,
   251  		},
   252  		{
   253  			"normal", args{key: key, field: [][]byte{[]byte("a"), []byte("b"), []byte("c")}}, 3,
   254  			[][]byte{[]byte("hash_data_01"), []byte("hash_data_02"), []byte("hash_data_03")}, false,
   255  		},
   256  		{
   257  			"normal-2", args{key: key, field: [][]byte{[]byte("a"), []byte("e"), []byte("c")}}, 3,
   258  			[][]byte{[]byte("hash_data_01"), nil, []byte("hash_data_03")}, false,
   259  		},
   260  	}
   261  	// test 1 field get
   262  	val, err := db.HMGet(key, []byte("a"))
   263  	assert.Nil(t, err)
   264  	assert.Equal(t, [][]byte{[]byte("hash_data_01")}, val)
   265  
   266  	for _, tt := range tests {
   267  		t.Run(tt.name, func(t *testing.T) {
   268  			vals, err := db.HMGet(tt.args.key, tt.args.field...)
   269  			assert.Equal(t, tt.wantLen, len(vals), "the result len is not the same!")
   270  			assert.Equal(t, tt.want, vals, "the result is not the same!")
   271  			if (err != nil) != tt.wantErr {
   272  				t.Errorf("db.HMGet() error = %v, wantErr= %v", err, tt.wantErr)
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  func TestRoseDB_HDel(t *testing.T) {
   279  	t.Run("fileio", func(t *testing.T) {
   280  		testRoseDBHDel(t, FileIO, KeyOnlyMemMode)
   281  	})
   282  	t.Run("mmap", func(t *testing.T) {
   283  		testRoseDBHDel(t, MMap, KeyValueMemMode)
   284  	})
   285  }
   286  
   287  func testRoseDBHDel(t *testing.T, ioType IOType, mode DataIndexMode) {
   288  	path := filepath.Join("/tmp", "rosedb")
   289  	opts := DefaultOptions(path)
   290  	opts.IoType = ioType
   291  	opts.IndexMode = mode
   292  	db, err := Open(opts)
   293  	assert.Nil(t, err)
   294  	defer destroyDB(db)
   295  
   296  	// not exist
   297  	setKey := []byte("my_set")
   298  	c1, err := db.HDel(setKey, GetKey(1), GetKey(2))
   299  	assert.Nil(t, err)
   300  	assert.Equal(t, 0, c1)
   301  
   302  	err = db.HSet(setKey, GetKey(1), GetValue16B())
   303  	assert.Nil(t, err)
   304  	err = db.HSet(setKey, GetKey(2), GetValue16B())
   305  	assert.Nil(t, err)
   306  	err = db.HSet(setKey, GetKey(3), GetValue16B())
   307  	assert.Nil(t, err)
   308  
   309  	c2, err := db.HDel(setKey, GetKey(3))
   310  	assert.Nil(t, err)
   311  	assert.Equal(t, 1, c2)
   312  
   313  	v1, err := db.HGet(setKey, GetKey(3))
   314  	assert.Nil(t, err)
   315  	assert.Nil(t, v1)
   316  }
   317  
   318  func TestRoseDB_HExists(t *testing.T) {
   319  	t.Run("fileio", func(t *testing.T) {
   320  		testRoseDBHExists(t, FileIO, KeyOnlyMemMode)
   321  	})
   322  	t.Run("mmap", func(t *testing.T) {
   323  		testRoseDBHExists(t, MMap, KeyValueMemMode)
   324  	})
   325  }
   326  
   327  func testRoseDBHExists(t *testing.T, ioType IOType, mode DataIndexMode) {
   328  	path := filepath.Join("/tmp", "rosedb")
   329  	opts := DefaultOptions(path)
   330  	opts.IoType = ioType
   331  	opts.IndexMode = mode
   332  	db, err := Open(opts)
   333  	assert.Nil(t, err)
   334  	defer destroyDB(db)
   335  
   336  	setKey := []byte("my_set")
   337  	err = db.HSet(setKey, GetKey(1), GetValue16B())
   338  	assert.Nil(t, err)
   339  
   340  	c1, err := db.HExists(setKey, GetKey(1))
   341  	assert.Nil(t, err)
   342  	assert.Equal(t, c1, true)
   343  
   344  	c2, err := db.HExists(setKey, GetKey(2))
   345  	assert.Nil(t, err)
   346  	assert.Equal(t, c2, false)
   347  }
   348  
   349  func TestRoseDB_HLen(t *testing.T) {
   350  	path := filepath.Join("/tmp", "rosedb")
   351  	opts := DefaultOptions(path)
   352  	db, err := Open(opts)
   353  	assert.Nil(t, err)
   354  	defer destroyDB(db)
   355  
   356  	hashKey := []byte("my_hash")
   357  	l1 := db.HLen(hashKey)
   358  	assert.Equal(t, 0, l1)
   359  
   360  	err = db.HSet(hashKey, GetKey(1), GetValue16B())
   361  	assert.Nil(t, err)
   362  	l2 := db.HLen(hashKey)
   363  	assert.Equal(t, 1, l2)
   364  
   365  	err = db.HSet(hashKey, GetKey(1), GetValue128B())
   366  	assert.Nil(t, err)
   367  
   368  	err = db.HSet(hashKey, GetKey(2), GetValue16B())
   369  	assert.Nil(t, err)
   370  	l3 := db.HLen(hashKey)
   371  	assert.Equal(t, 2, l3)
   372  
   373  	writeCount := 1000
   374  	for i := 0; i < writeCount; i++ {
   375  		err := db.HSet(hashKey, GetKey(i+100), GetValue16B())
   376  		assert.Nil(t, err)
   377  	}
   378  	l4 := db.HLen(hashKey)
   379  	assert.Equal(t, writeCount+2, l4)
   380  }
   381  
   382  func TestRoseDB_DiscardStat_Hash(t *testing.T) {
   383  	helper := func(isDelete bool) {
   384  		path := filepath.Join("/tmp", "rosedb")
   385  		opts := DefaultOptions(path)
   386  		opts.LogFileSizeThreshold = 64 << 20
   387  		db, err := Open(opts)
   388  		assert.Nil(t, err)
   389  		defer destroyDB(db)
   390  
   391  		hashKey := []byte("my_hash")
   392  		writeCount := 500000
   393  		for i := 0; i < writeCount; i++ {
   394  			err := db.HSet(hashKey, GetKey(i), GetValue128B())
   395  			assert.Nil(t, err)
   396  		}
   397  
   398  		if isDelete {
   399  			for i := 0; i < writeCount/2; i++ {
   400  				_, err := db.HDel(hashKey, GetKey(i))
   401  				assert.Nil(t, err)
   402  			}
   403  		} else {
   404  			for i := 0; i < writeCount/2; i++ {
   405  				err := db.HSet(hashKey, GetKey(i), GetValue128B())
   406  				assert.Nil(t, err)
   407  			}
   408  		}
   409  		_ = db.Sync()
   410  		ccl, err := db.discards[Hash].getCCL(10, 0.5)
   411  		assert.Nil(t, err)
   412  		assert.Equal(t, 1, len(ccl))
   413  	}
   414  
   415  	t.Run("rewrite", func(t *testing.T) {
   416  		helper(false)
   417  	})
   418  
   419  	t.Run("delete", func(t *testing.T) {
   420  		helper(true)
   421  	})
   422  }
   423  
   424  func TestRoseDB_HashGC(t *testing.T) {
   425  	path := filepath.Join("/tmp", "rosedb")
   426  	opts := DefaultOptions(path)
   427  	opts.LogFileSizeThreshold = 64 << 20
   428  	db, err := Open(opts)
   429  	assert.Nil(t, err)
   430  	defer destroyDB(db)
   431  
   432  	hashKey := []byte("my_hash")
   433  	writeCount := 500000
   434  	for i := 0; i < writeCount; i++ {
   435  		err := db.HSet(hashKey, GetKey(i), GetValue16B())
   436  		assert.Nil(t, err)
   437  	}
   438  	for i := 0; i < writeCount/2; i++ {
   439  		_, err := db.HDel(hashKey, GetKey(i))
   440  		assert.Nil(t, err)
   441  	}
   442  
   443  	err = db.RunLogFileGC(Hash, 0, 0.4)
   444  	assert.Nil(t, err)
   445  
   446  	l1 := db.HLen(hashKey)
   447  	assert.Equal(t, writeCount/2, l1)
   448  }
   449  
   450  func TestRoseDB_HKeys(t *testing.T) {
   451  	path := filepath.Join("/tmp", "rosedb")
   452  	opts := DefaultOptions(path)
   453  	db, err := Open(opts)
   454  	assert.Nil(t, err)
   455  	defer destroyDB(db)
   456  
   457  	hashKey := []byte("my_hash")
   458  	keys, err := db.HKeys(hashKey)
   459  	assert.Nil(t, err)
   460  	assert.Equal(t, 0, len(keys))
   461  
   462  	err = db.HSet(hashKey, GetKey(1), GetValue16B())
   463  	assert.Nil(t, err)
   464  	keys, err = db.HKeys(hashKey)
   465  	assert.Nil(t, err)
   466  	assert.Equal(t, 1, len(keys))
   467  	assert.Equal(t, GetKey(1), keys[0])
   468  
   469  	err = db.HSet(hashKey, GetKey(1), GetValue128B())
   470  	assert.Nil(t, err)
   471  	keys, err = db.HKeys(hashKey)
   472  	assert.Nil(t, err)
   473  	assert.Equal(t, 1, len(keys))
   474  	assert.Equal(t, GetKey(1), keys[0])
   475  
   476  	err = db.HSet(hashKey, GetKey(2), GetValue16B())
   477  	assert.Nil(t, err)
   478  	keys, err = db.HKeys(hashKey)
   479  	assert.Nil(t, err)
   480  	assert.Equal(t, 2, len(keys))
   481  	assert.Equal(t, [][]byte{GetKey(1), GetKey(2)}, keys)
   482  
   483  	writeCount := 1000
   484  	for i := 0; i < writeCount; i++ {
   485  		err := db.HSet(hashKey, GetKey(i+100), GetValue16B())
   486  		assert.Nil(t, err)
   487  	}
   488  	keys, err = db.HKeys(hashKey)
   489  	assert.Nil(t, err)
   490  	for i := 0; i < writeCount; i++ {
   491  		assert.Equal(t, GetKey(i+100), keys[i+2])
   492  	}
   493  }
   494  
   495  func TestRoseDB_HVals(t *testing.T) {
   496  	cases := []struct {
   497  		IOType
   498  		DataIndexMode
   499  	}{
   500  		{FileIO, KeyValueMemMode},
   501  		{FileIO, KeyOnlyMemMode},
   502  		{MMap, KeyValueMemMode},
   503  		{MMap, KeyOnlyMemMode},
   504  	}
   505  
   506  	oneRun := func(t *testing.T, opts Options) {
   507  		db, err := Open(opts)
   508  		assert.Nil(t, err)
   509  		defer destroyDB(db)
   510  
   511  		hashKey := []byte("my_hash")
   512  		vals, err := db.HVals(hashKey)
   513  		assert.Nil(t, err)
   514  		assert.Equal(t, 0, len(vals))
   515  
   516  		val16B := GetValue16B()
   517  		err = db.HSet(hashKey, GetKey(1), val16B)
   518  		assert.Nil(t, err)
   519  		vals, err = db.HVals(hashKey)
   520  		assert.Nil(t, err)
   521  		assert.Equal(t, 1, len(vals))
   522  		assert.Equal(t, val16B, vals[0])
   523  
   524  		val128B := GetValue128B()
   525  		err = db.HSet(hashKey, GetKey(1), val128B)
   526  		assert.Nil(t, err)
   527  		vals, err = db.HVals(hashKey)
   528  		assert.Nil(t, err)
   529  		assert.Equal(t, 1, len(vals))
   530  		assert.Equal(t, val128B, vals[0])
   531  
   532  		err = db.HSet(hashKey, GetKey(2), val16B)
   533  		assert.Nil(t, err)
   534  		vals, err = db.HVals(hashKey)
   535  		assert.Nil(t, err)
   536  		assert.Equal(t, 2, len(vals))
   537  		assert.Equal(t, [][]byte{val128B, val16B}, vals)
   538  
   539  		val16B = GetValue16B()
   540  		writeCount := 1000
   541  		for i := 0; i < writeCount; i++ {
   542  			err := db.HSet(hashKey, GetKey(i+100), val16B)
   543  			assert.Nil(t, err)
   544  		}
   545  		vals, err = db.HVals(hashKey)
   546  		assert.Nil(t, err)
   547  		for i := 0; i < writeCount; i++ {
   548  			assert.Equal(t, val16B, vals[i+2])
   549  		}
   550  	}
   551  
   552  	for _, c := range cases {
   553  		path := filepath.Join("/tmp", "rosedb")
   554  		opts := DefaultOptions(path)
   555  		opts.IoType = c.IOType
   556  		opts.IndexMode = c.DataIndexMode
   557  		oneRun(t, opts)
   558  	}
   559  }
   560  
   561  func TestRoseDB_HGetAll(t *testing.T) {
   562  	cases := []struct {
   563  		IOType
   564  		DataIndexMode
   565  	}{
   566  		{FileIO, KeyValueMemMode},
   567  		{FileIO, KeyOnlyMemMode},
   568  		{MMap, KeyValueMemMode},
   569  		{MMap, KeyOnlyMemMode},
   570  	}
   571  
   572  	oneRun := func(t *testing.T, opts Options) {
   573  		db, err := Open(opts)
   574  		assert.Nil(t, err)
   575  		defer destroyDB(db)
   576  
   577  		hashKey := []byte("my_hash")
   578  		pairs, err := db.HGetAll(hashKey)
   579  		assert.Nil(t, err)
   580  		assert.Equal(t, 0, len(pairs))
   581  
   582  		// one
   583  		val16B := GetValue16B()
   584  		err = db.HSet(hashKey, GetKey(1), val16B)
   585  		assert.Nil(t, err)
   586  		pairs, err = db.HGetAll(hashKey)
   587  		assert.Nil(t, err)
   588  		assert.Equal(t, 2, len(pairs))
   589  		assert.Equal(t, [][]byte{GetKey(1), val16B}, pairs)
   590  
   591  		val128B := GetValue128B()
   592  		err = db.HSet(hashKey, GetKey(1), val128B)
   593  		assert.Nil(t, err)
   594  		pairs, err = db.HGetAll(hashKey)
   595  		assert.Nil(t, err)
   596  		assert.Equal(t, 2, len(pairs))
   597  		assert.Equal(t, [][]byte{GetKey(1), val128B}, pairs)
   598  
   599  		// two
   600  		err = db.HSet(hashKey, GetKey(2), val16B)
   601  		assert.Nil(t, err)
   602  		pairs, err = db.HGetAll(hashKey)
   603  		assert.Nil(t, err)
   604  		assert.Equal(t, 4, len(pairs))
   605  		assert.Equal(t, GetKey(1), pairs[0])
   606  		assert.Equal(t, [][]byte{GetKey(1), val128B, GetKey(2), val16B}, pairs)
   607  	}
   608  
   609  	for _, c := range cases {
   610  		path := filepath.Join("/tmp", "rosedb")
   611  		opts := DefaultOptions(path)
   612  		opts.IoType = c.IOType
   613  		opts.IndexMode = c.DataIndexMode
   614  		oneRun(t, opts)
   615  	}
   616  }
   617  
   618  func TestRoseDB_HStrLen(t *testing.T) {
   619  	cases := []struct {
   620  		IOType
   621  		DataIndexMode
   622  	}{
   623  		{FileIO, KeyValueMemMode},
   624  		{FileIO, KeyOnlyMemMode},
   625  		{MMap, KeyValueMemMode},
   626  		{MMap, KeyOnlyMemMode},
   627  	}
   628  	oneRun := func(t *testing.T, opts Options) {
   629  		db, err := Open(opts)
   630  		assert.Nil(t, err)
   631  		defer destroyDB(db)
   632  
   633  		hashKey := []byte("my_hash")
   634  		key1 := GetKey(1)
   635  		kLen := db.HStrLen(hashKey, key1)
   636  		assert.Nil(t, err)
   637  		assert.Equal(t, 0, kLen)
   638  
   639  		for i := 0; i < 10; i++ {
   640  			key := GetKey(i)
   641  			val := GetValue(i)
   642  			err = db.HSet(hashKey, key, val)
   643  			assert.Nil(t, err)
   644  			kLen = db.HStrLen(hashKey, key)
   645  			assert.Nil(t, err)
   646  			assert.Equal(t, kLen, len(val))
   647  		}
   648  	}
   649  
   650  	for _, c := range cases {
   651  		path := filepath.Join("/tmp", "rosedb")
   652  		opts := DefaultOptions(path)
   653  		opts.IoType = c.IOType
   654  		opts.IndexMode = c.DataIndexMode
   655  		oneRun(t, opts)
   656  	}
   657  }
   658  
   659  func TestRoseDB_HScan(t *testing.T) {
   660  	path := filepath.Join("/tmp", "rosedb")
   661  	opts := DefaultOptions(path)
   662  	db, err := Open(opts)
   663  	assert.Nil(t, err)
   664  	defer destroyDB(db)
   665  
   666  	setKey := []byte("my_set")
   667  	err = db.HSet(setKey, GetKey(32), GetValue16B())
   668  	assert.Nil(t, err)
   669  	err = db.HSet(setKey, GetKey(21), GetValue16B())
   670  	assert.Nil(t, err)
   671  	err = db.HSet(setKey, GetKey(14), GetValue16B())
   672  	assert.Nil(t, err)
   673  	err = db.HSet(setKey, GetKey(43), GetValue16B())
   674  	assert.Nil(t, err)
   675  
   676  	values, err := db.HScan(setKey, []byte("kv"), "", 100)
   677  	assert.Nil(t, err)
   678  	assert.Equal(t, 8, len(values))
   679  }
   680  
   681  func TestRoseDB_HIncrBy(t *testing.T) {
   682  	cases := []struct {
   683  		IOType
   684  		DataIndexMode
   685  	}{
   686  		{FileIO, KeyValueMemMode},
   687  		{FileIO, KeyOnlyMemMode},
   688  		{MMap, KeyValueMemMode},
   689  		{MMap, KeyOnlyMemMode},
   690  	}
   691  
   692  	oneRun := func(t *testing.T, opts Options) {
   693  		db, err := Open(opts)
   694  		assert.Nil(t, err)
   695  		defer destroyDB(db)
   696  
   697  		// both key and field do not exist
   698  		hashKey := []byte("my_hash")
   699  		field1 := []byte("field1")
   700  		valInt64, err := db.HIncrBy(hashKey, field1, 1)
   701  		assert.Nil(t, err)
   702  		assert.Equal(t, int64(1), valInt64)
   703  		valByte, err := db.HGet(hashKey, field1)
   704  		assert.Nil(t, err)
   705  		assert.Equal(t, []byte("1"), valByte)
   706  
   707  		// field does not exist
   708  		field2 := []byte("field2")
   709  		valInt64, err = db.HIncrBy(hashKey, field2, 2)
   710  		assert.Nil(t, err)
   711  		assert.Equal(t, int64(2), valInt64)
   712  		valByte, err = db.HGet(hashKey, field2)
   713  		assert.Nil(t, err)
   714  		assert.Equal(t, []byte("2"), valByte)
   715  
   716  		// increment(1 + 2)
   717  		valInt64, err = db.HIncrBy(hashKey, field1, 2)
   718  		assert.Nil(t, err)
   719  		assert.Equal(t, int64(3), valInt64)
   720  		valByte, err = db.HGet(hashKey, field1)
   721  		assert.Nil(t, err)
   722  		assert.Equal(t, []byte("3"), valByte)
   723  
   724  		// negative incr(3 - 4)
   725  		valInt64, err = db.HIncrBy(hashKey, field1, -4)
   726  		assert.Nil(t, err)
   727  		assert.Equal(t, int64(-1), valInt64)
   728  		valByte, err = db.HGet(hashKey, field1)
   729  		assert.Nil(t, err)
   730  		assert.Equal(t, []byte("-1"), valByte)
   731  
   732  		// overflow value-min(-1 + math.MinInt64)
   733  		_, err = db.HIncrBy(hashKey, field1, math.MinInt64)
   734  		assert.Equal(t, ErrIntegerOverflow, err)
   735  
   736  		// overflow value-max(2 + math.MaxInt64)
   737  		_, err = db.HIncrBy(hashKey, field2, math.MaxInt64)
   738  		assert.Equal(t, ErrIntegerOverflow, err)
   739  
   740  		// wrong value type
   741  		wrongField := []byte("wrong_field")
   742  		err = db.HSet(hashKey, wrongField, []byte("wrong_val"))
   743  		assert.Nil(t, err)
   744  		_, err = db.HIncrBy(hashKey, wrongField, 1)
   745  		assert.Equal(t, ErrWrongValueType, err)
   746  	}
   747  
   748  	for _, c := range cases {
   749  		path := filepath.Join("/tmp", "rosedb")
   750  		opts := DefaultOptions(path)
   751  		opts.IoType = c.IOType
   752  		opts.IndexMode = c.DataIndexMode
   753  		oneRun(t, opts)
   754  	}
   755  }
   756  
   757  func TestRoseDB_HRandField(t *testing.T) {
   758  	cases := []struct {
   759  		IOType
   760  		DataIndexMode
   761  	}{
   762  		{FileIO, KeyValueMemMode},
   763  		{FileIO, KeyOnlyMemMode},
   764  		{MMap, KeyValueMemMode},
   765  		{MMap, KeyOnlyMemMode},
   766  	}
   767  
   768  	hashKey := []byte("my_hash")
   769  	field1, field2, field3, field4, field5 := []byte("field1"), []byte("field2"), []byte("field3"), []byte("field4"), []byte("field5")
   770  	value1, value2, value3, value4, value5 := []byte("value1"), []byte("value2"), []byte("value3"), []byte("value4"), []byte("value5")
   771  	fields := [][]byte{field1, field2, field3, field4, field5}
   772  	values := [][]byte{value1, value2, value3, value4, value5}
   773  
   774  	distinctFunc := func(pairs [][]byte, count int, withValues bool) {
   775  		if count > len(fields) {
   776  			count = len(fields)
   777  		}
   778  		var pairLength = 1
   779  		if withValues {
   780  			pairLength = 2
   781  		}
   782  		pairCount := len(pairs) / pairLength
   783  		assert.Equal(t, count, pairCount)
   784  		// only key of the pair should be able to be compared.
   785  		for i := 0; i < pairCount; i++ {
   786  			assert.Contains(t, fields, pairs[i*pairLength])
   787  			for j := 0; j < pairCount; j++ {
   788  				if i == j {
   789  					continue
   790  				}
   791  				assert.NotEqual(t, pairs[i*pairLength], pairs[j*pairLength])
   792  			}
   793  		}
   794  	}
   795  	duplicationFunc := func(pairs [][]byte, count int, withValues bool) {
   796  		var pairLength = 1
   797  		if withValues {
   798  			pairLength = 2
   799  		}
   800  		pairCount := len(pairs) / pairLength
   801  		assert.Equal(t, count, pairCount)
   802  		for i := 0; i < pairCount; i++ {
   803  			assert.Contains(t, fields, pairs[i*pairLength])
   804  			if withValues {
   805  				assert.Contains(t, values, pairs[i*pairLength+1])
   806  			}
   807  		}
   808  	}
   809  
   810  	run := func(t *testing.T, opts Options) {
   811  		db, err := Open(opts)
   812  		assert.Nil(t, err)
   813  		defer destroyDB(db)
   814  		const withValues = false
   815  
   816  		_ = db.HSet(hashKey, field1, value1, field2, value2, field3, value3, field4, value4, field5, value5)
   817  
   818  		// empty
   819  		keys, err := db.HRandField(hashKey, 0, withValues)
   820  		assert.Nil(t, err)
   821  		assert.Equal(t, 0, len(keys))
   822  
   823  		// key not found
   824  		keys, err = db.HRandField([]byte("key-not-found"), 1, withValues)
   825  		assert.Nil(t, err)
   826  		assert.Equal(t, 0, len(keys))
   827  
   828  		// return a random field from the hash value
   829  		keys, err = db.HRandField(hashKey, 1, withValues)
   830  		assert.Nil(t, err)
   831  		assert.Equal(t, 1, len(keys))
   832  		assert.Contains(t, fields, keys[0])
   833  
   834  		// return random fields from the hash value by count i
   835  		for i := 1; i <= 10; i++ {
   836  			keys, err = db.HRandField(hashKey, i, false)
   837  			assert.Nil(t, err)
   838  			distinctFunc(keys, i, withValues)
   839  		}
   840  
   841  		// return the same field multiple times by count -i
   842  		for i := 1; i <= 10; i++ {
   843  			keys, err = db.HRandField(hashKey, -i, withValues)
   844  			assert.Nil(t, err)
   845  			duplicationFunc(keys, i, withValues)
   846  		}
   847  	}
   848  
   849  	runWithValues := func(t *testing.T, opts Options) {
   850  		db, err := Open(opts)
   851  		assert.Nil(t, err)
   852  		defer destroyDB(db)
   853  		const withValues = true
   854  
   855  		_ = db.HSet(hashKey, field1, value1, field2, value2, field3, value3, field4, value4, field5, value5)
   856  
   857  		// empty
   858  		pairs, err := db.HRandField(hashKey, 0, withValues)
   859  		assert.Nil(t, err)
   860  		assert.Equal(t, 0, len(pairs))
   861  
   862  		// key not found
   863  		pairs, err = db.HRandField([]byte("key-not-found"), 1, withValues)
   864  		assert.Nil(t, err)
   865  		assert.Equal(t, 0, len(pairs))
   866  
   867  		// return a random field from the hash value
   868  		pairs, err = db.HRandField(hashKey, 1, withValues)
   869  		assert.Nil(t, err)
   870  		assert.Equal(t, 2, len(pairs))
   871  		assert.Contains(t, fields, pairs[0])
   872  		assert.Contains(t, values, pairs[1])
   873  
   874  		// return random pairs from the hash value by count i
   875  		for i := 1; i <= 10; i++ {
   876  			pairs, err = db.HRandField(hashKey, i, withValues)
   877  			assert.Nil(t, err)
   878  			distinctFunc(pairs, i, withValues)
   879  		}
   880  
   881  		// return the same pairs multiple times by count -i
   882  		for i := 1; i <= 10; i++ {
   883  			pairs, err = db.HRandField(hashKey, -i, withValues)
   884  			assert.Nil(t, err)
   885  			duplicationFunc(pairs, i, withValues)
   886  		}
   887  	}
   888  
   889  	for _, c := range cases {
   890  		path := filepath.Join("/tmp", "rosedb")
   891  		opts := DefaultOptions(path)
   892  		opts.IoType = c.IOType
   893  		opts.IndexMode = c.DataIndexMode
   894  		run(t, opts)
   895  		runWithValues(t, opts)
   896  	}
   897  }