github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/roaringset/cursor_test.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package roaringset 13 14 import ( 15 "testing" 16 17 "github.com/stretchr/testify/assert" 18 ) 19 20 func TestCombinedCursor(t *testing.T) { 21 bst1 := createBst(t, []bstIn{ 22 { 23 key: "aaa", 24 additions: []uint64{1}, 25 deletions: []uint64{}, 26 }, 27 { 28 key: "bbb", 29 additions: []uint64{22}, 30 deletions: []uint64{}, 31 }, 32 { 33 key: "ccc", 34 additions: []uint64{333}, 35 deletions: []uint64{}, 36 }, 37 { 38 key: "ddd", 39 additions: []uint64{4444}, 40 deletions: []uint64{}, 41 }, 42 }) 43 44 bst2 := createBst(t, []bstIn{ 45 { 46 key: "aaa", 47 additions: []uint64{2, 3}, 48 deletions: []uint64{1}, 49 }, 50 { 51 key: "bbb", 52 additions: []uint64{33}, 53 deletions: []uint64{22}, 54 }, 55 { 56 key: "ggg", 57 additions: []uint64{7777777, 8888888}, 58 deletions: []uint64{}, 59 }, 60 }) 61 62 bst3 := createBst(t, []bstIn{ 63 { 64 key: "bbb", 65 additions: []uint64{22}, 66 deletions: []uint64{}, 67 }, 68 { 69 key: "ccc", 70 additions: []uint64{}, 71 deletions: []uint64{333}, 72 }, 73 { 74 key: "eee", 75 additions: []uint64{55555, 66666}, 76 deletions: []uint64{}, 77 }, 78 { 79 key: "fff", 80 additions: []uint64{666666}, 81 deletions: []uint64{}, 82 }, 83 { 84 key: "hhh", 85 additions: []uint64{999999999}, 86 deletions: []uint64{111111111, 222222222, 333333333}, 87 }, 88 }) 89 90 expected := []struct { 91 key string 92 values []uint64 93 }{ 94 { // 0 95 key: "aaa", 96 values: []uint64{2, 3}, 97 }, 98 { // 1 99 key: "bbb", 100 values: []uint64{22, 33}, 101 }, 102 { // 2 103 key: "ddd", 104 values: []uint64{4444}, 105 }, 106 { // 3 107 key: "eee", 108 values: []uint64{55555, 66666}, 109 }, 110 { // 4 111 key: "fff", 112 values: []uint64{666666}, 113 }, 114 { // 5 115 key: "ggg", 116 values: []uint64{7777777, 8888888}, 117 }, 118 { // 6 119 key: "hhh", 120 values: []uint64{999999999}, 121 }, 122 } 123 124 t.Run("default cursor", func(t *testing.T) { 125 t.Run("start from beginning", func(t *testing.T) { 126 cursor := createCursor(t, bst1, bst2, bst3) 127 128 key, bm := cursor.First() 129 130 assert.Equal(t, []byte(expected[0].key), key) 131 assert.Equal(t, len(expected[0].values), bm.GetCardinality()) 132 for _, v := range expected[0].values { 133 assert.True(t, bm.Contains(v)) 134 } 135 }) 136 137 t.Run("start from beginning and go through all", func(t *testing.T) { 138 cursor := createCursor(t, bst1, bst2, bst3) 139 140 i := 0 // 1st match is "aaa" 141 for key, bm := cursor.First(); key != nil; key, bm = cursor.Next() { 142 assert.Equal(t, []byte(expected[i].key), key) 143 assert.Equal(t, len(expected[i].values), bm.GetCardinality()) 144 for _, v := range expected[i].values { 145 assert.True(t, bm.Contains(v)) 146 } 147 i++ 148 } 149 }) 150 151 t.Run("start from beginning using Next and go through all", func(t *testing.T) { 152 cursor := createCursor(t, bst1, bst2, bst3) 153 154 i := 0 // 1st match is "aaa" 155 for key, bm := cursor.Next(); key != nil; key, bm = cursor.Next() { 156 assert.Equal(t, []byte(expected[i].key), key) 157 assert.Equal(t, len(expected[i].values), bm.GetCardinality()) 158 for _, v := range expected[i].values { 159 assert.True(t, bm.Contains(v)) 160 } 161 i++ 162 } 163 }) 164 165 t.Run("seek matching element and go through rest", func(t *testing.T) { 166 cursor := createCursor(t, bst1, bst2, bst3) 167 168 i := 2 // 1st match is "ddd" 169 matching := []byte("ddd") 170 for key, bm := cursor.Seek(matching); key != nil; key, bm = cursor.Next() { 171 assert.Equal(t, []byte(expected[i].key), key) 172 assert.Equal(t, len(expected[i].values), bm.GetCardinality()) 173 for _, v := range expected[i].values { 174 assert.True(t, bm.Contains(v)) 175 } 176 i++ 177 } 178 }) 179 180 t.Run("seek non-matching element and go through rest", func(t *testing.T) { 181 cursor := createCursor(t, bst1, bst2, bst3) 182 183 i := 4 // 1st match is "fff" 184 nonMatching := []byte("efg") 185 for key, bm := cursor.Seek(nonMatching); key != nil; key, bm = cursor.Next() { 186 assert.Equal(t, []byte(expected[i].key), key) 187 assert.Equal(t, len(expected[i].values), bm.GetCardinality()) 188 for _, v := range expected[i].values { 189 assert.True(t, bm.Contains(v)) 190 } 191 i++ 192 } 193 }) 194 195 t.Run("seek missing element", func(t *testing.T) { 196 cursor := createCursor(t, bst1, bst2, bst3) 197 198 missing := []byte("lll") 199 key, bm := cursor.Seek(missing) 200 201 assert.Nil(t, key) 202 assert.NotNil(t, bm) 203 assert.Equal(t, 0, bm.GetCardinality()) 204 }) 205 206 t.Run("next after seek missing element does not change cursor's position", func(t *testing.T) { 207 cursor := createCursor(t, bst1, bst2, bst3) 208 209 key1, _ := cursor.First() 210 211 missing := []byte("lll") 212 cursor.Seek(missing) 213 214 key2, _ := cursor.Next() 215 216 assert.Equal(t, []byte("aaa"), key1) 217 assert.Equal(t, []byte("bbb"), key2) 218 }) 219 220 t.Run("next after last is nil/empty", func(t *testing.T) { 221 cursor := createCursor(t, bst1, bst2, bst3) 222 223 last := []byte("hhh") 224 cursor.Seek(last) 225 key, bm := cursor.Next() 226 227 assert.Nil(t, key) 228 assert.NotNil(t, bm) 229 assert.Equal(t, 0, bm.GetCardinality()) 230 }) 231 232 t.Run("first after final/empty next", func(t *testing.T) { 233 cursor := createCursor(t, bst1, bst2, bst3) 234 235 last := []byte("hhh") 236 cursor.Seek(last) 237 cursor.Next() 238 key, bm := cursor.First() 239 240 assert.Equal(t, []byte(expected[0].key), key) 241 assert.Equal(t, len(expected[0].values), bm.GetCardinality()) 242 for _, v := range expected[0].values { 243 assert.True(t, bm.Contains(v)) 244 } 245 }) 246 247 t.Run("seek after final/empty next", func(t *testing.T) { 248 cursor := createCursor(t, bst1, bst2, bst3) 249 250 last := []byte("hhh") 251 matching := []byte("eee") 252 cursor.Seek(last) 253 cursor.Next() 254 key, bm := cursor.Seek(matching) 255 256 assert.Equal(t, []byte(expected[3].key), key) 257 assert.Equal(t, len(expected[3].values), bm.GetCardinality()) 258 for _, v := range expected[3].values { 259 assert.True(t, bm.Contains(v)) 260 } 261 }) 262 }) 263 264 t.Run("cursor key only", func(t *testing.T) { 265 t.Run("start from beginning", func(t *testing.T) { 266 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 267 268 key, bm := cursor.First() 269 270 assert.Equal(t, []byte(expected[0].key), key) 271 assert.Nil(t, bm) 272 }) 273 274 t.Run("start from beginning and go through all", func(t *testing.T) { 275 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 276 277 i := 0 // 1st match is "aaa" 278 for key, bm := cursor.First(); key != nil; key, bm = cursor.Next() { 279 assert.Equal(t, []byte(expected[i].key), key) 280 assert.Nil(t, bm) 281 i++ 282 } 283 }) 284 285 t.Run("start from beginning using Next and go through all", func(t *testing.T) { 286 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 287 288 i := 0 // 1st match is "aaa" 289 for key, bm := cursor.Next(); key != nil; key, bm = cursor.Next() { 290 assert.Equal(t, []byte(expected[i].key), key) 291 assert.Nil(t, bm) 292 i++ 293 } 294 }) 295 296 t.Run("seek matching element and go through rest", func(t *testing.T) { 297 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 298 299 i := 2 // 1st match is "ddd" 300 matching := []byte("ddd") 301 for key, bm := cursor.Seek(matching); key != nil; key, bm = cursor.Next() { 302 assert.Equal(t, []byte(expected[i].key), key) 303 assert.Nil(t, bm) 304 i++ 305 } 306 }) 307 308 t.Run("seek non-matching element and go through rest", func(t *testing.T) { 309 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 310 311 i := 4 // 1st match is "fff" 312 nonMatching := []byte("efg") 313 for key, bm := cursor.Seek(nonMatching); key != nil; key, bm = cursor.Next() { 314 assert.Equal(t, []byte(expected[i].key), key) 315 assert.Nil(t, bm) 316 i++ 317 } 318 }) 319 320 t.Run("seek missing element", func(t *testing.T) { 321 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 322 323 missing := []byte("lll") 324 key, bm := cursor.Seek(missing) 325 326 assert.Nil(t, key) 327 assert.Nil(t, bm) 328 }) 329 330 t.Run("next after seek missing element does not change cursor's position", func(t *testing.T) { 331 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 332 333 key1, _ := cursor.First() 334 335 missing := []byte("lll") 336 cursor.Seek(missing) 337 338 key2, _ := cursor.Next() 339 340 assert.Equal(t, []byte("aaa"), key1) 341 assert.Equal(t, []byte("bbb"), key2) 342 }) 343 344 t.Run("next after last is nil/empty", func(t *testing.T) { 345 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 346 347 last := []byte("hhh") 348 cursor.Seek(last) 349 key, bm := cursor.Next() 350 351 assert.Nil(t, key) 352 assert.Nil(t, bm) 353 }) 354 355 t.Run("first after final/empty next", func(t *testing.T) { 356 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 357 358 last := []byte("hhh") 359 cursor.Seek(last) 360 cursor.Next() 361 key, bm := cursor.First() 362 363 assert.Equal(t, []byte(expected[0].key), key) 364 assert.Nil(t, bm) 365 }) 366 367 t.Run("seek after final/empty next", func(t *testing.T) { 368 cursor := createCursorKeyOnly(t, bst1, bst2, bst3) 369 370 last := []byte("hhh") 371 matching := []byte("eee") 372 cursor.Seek(last) 373 cursor.Next() 374 key, bm := cursor.Seek(matching) 375 376 assert.Equal(t, []byte(expected[3].key), key) 377 assert.Nil(t, bm) 378 }) 379 }) 380 } 381 382 type bstIn struct { 383 key string 384 additions []uint64 385 deletions []uint64 386 } 387 388 func createBst(t *testing.T, in []bstIn) *BinarySearchTree { 389 bst := &BinarySearchTree{} 390 for i := range in { 391 bst.Insert([]byte(in[i].key), Insert{Additions: in[i].additions, Deletions: in[i].deletions}) 392 } 393 return bst 394 } 395 396 func createCursor(t *testing.T, bsts ...*BinarySearchTree) *CombinedCursor { 397 innerCursors := []InnerCursor{} 398 for _, bst := range bsts { 399 innerCursors = append(innerCursors, NewBinarySearchTreeCursor(bst)) 400 } 401 return NewCombinedCursor(innerCursors, false) 402 } 403 404 func createCursorKeyOnly(t *testing.T, bsts ...*BinarySearchTree) *CombinedCursor { 405 c := createCursor(t, bsts...) 406 c.keyOnly = true 407 return c 408 }