Commit 2a93ae07 authored by Kirill Smelkov's avatar Kirill Smelkov

Add test for splitX bug fix covering logic handling splits on edges

Commit 5c732b36 (Fix splitX bug.) in particular modified splitX to redo
the search on current level when key hits exactly kx slot in splitted X.

However this is not tested at all as with current tests nothing breaks with the
following patch:

	@@ -546,10 +546,11 @@ func (t *Tree) Set(k interface{} /*K*/, v interface{} /*V*/) {
	                i, ok := t.find(q, k)
	                if ok {
	                        switch x := q.(type) {
	                        case *x:
	                                if x.c > 2*kx {
	+                                       panic("splitX ; ok=T")
	                                        x, i = t.splitX(p, x, pi, i)
	                                }
	                                pi = i + 1
	                                p = x
	                                q = x.x[i+1].ch

The relookup handling is needed exactly for ok=true case bacause if key is in
xk slot there are 2 cases:

1) key < x.x[xk].k  : here splitX is ok to let search follow left split child
2) key = x.x[xk].k  : here splitX needs to let search follow right split child (*)

and splitX, when knowing key comes from xk entry, does not bother itself with
deciding which way to go (1 or 2) and for this case just follows up to make the
search relookup current level after split.

We can make existing tests cover this logic by raising N in TestSetGet2.
However for kx=32, kd=32 N has to be increased more than 100x which, on my
computer, increases the time to run TestSetGet2 from 0.5s to more than 200s,
and still with it it only covers case when X does not have a parent.

So instead of increasing N to be very large and slow down testing, let's add
explicit test for this particular case.

(*) reminder:

`i, ok := find(q, k)` finds i corresponding to entry in q with min(k' : k <= k')
(entry at i=q.c is always handled as with k'=∞)

NOTE the "or-equal" in comparision. However keys in data (d) and index (x) page
entries are treated differently. When find finds an entry with exact match (ok=true):

- for d: d[i] is used

	https://github.com/cznic/b/blob/aaaa43c9/btree.go#L409
	https://github.com/cznic/b/blob/aaaa43c9/btree.go#L558

- for x: x[i+1] is used:

	https://github.com/cznic/b/blob/aaaa43c9/btree.go#L406
	https://github.com/cznic/b/blob/aaaa43c9/btree.go#L555

in other words keys located at index page entries says: all keys in child are
strictly less than this key.

This is probably so organized to help splitting - so split can pick lowest data
entry from right data page and use its key as key in parent index entry for
left data sibling directly.
parent 0f0644ab
......@@ -292,6 +292,71 @@ func TestPrealloc(*testing.T) {
r.Close()
}
func TestSplitXOnEdge(t *testing.T) {
// verify how splitX works when splitting X for k pointing directly at split edge
tr := TreeNew(cmp)
// one index page with 2*kx+2 elements (last has .k=∞ so x.c=2*kx+1)
// which will splitX on next Set
for i := 0; i <= (2*kx + 1) * 2*kd; i++ {
// odd keys are left to be filled in second test
tr.Set(2*i, 2*i)
}
x0 := tr.r.(*x)
if x0.c != 2*kx+1 {
t.Fatalf("x0.c: %v ; expected %v", x0.c, 2*kx+1)
}
// set element with k directly at x0[kx].k
kedge := 2 * (kx + 1) * (2*kd)
if x0.x[kx].k != kedge {
t.Fatalf("edge key before splitX: %v ; expected %v", x0.x[kx].k, kedge)
}
tr.Set(kedge, 777)
// if splitX was wrong kedge:777 would land into wrong place with Get failing
v, ok := tr.Get(kedge)
if !(v==777 && ok) {
t.Fatalf("after splitX: Get(%v) -> %v, %v ; expected 777, true", v, ok)
}
// now check the same when splitted X has parent
xr := tr.r.(*x)
if xr.c != 1 { // second x comes with k=∞ with .c index
t.Fatalf("after splitX: xr.c: %v ; expected 1", xr.c)
}
if xr.x[0].ch != x0 {
t.Fatal("xr[0].ch is not x0")
}
for i := 0; i <= (2*kx) * kd; i++ {
tr.Set(2*i+1, 2*i+1)
}
// check x0 is in pre-splitX condition and still at the right place
if x0.c != 2*kx+1 {
t.Fatalf("x0.c: %v ; expected %v", x0.c, 2*kx+1)
}
if xr.x[0].ch != x0 {
t.Fatal("xr[0].ch is not x0")
}
// set element with k directly at x0[kx].k
kedge = (kx + 1) * (2*kd)
if x0.x[kx].k != kedge {
t.Fatalf("edge key before splitX: %v ; expected %v", x0.x[kx].k, kedge)
}
tr.Set(kedge, 888)
// if splitX was wrong kedge:888 would land into wrong place
v, ok = tr.Get(kedge)
if !(v==888 && ok) {
t.Fatalf("after splitX: Get(%v) -> %v, %v ; expected 888, true", v, ok)
}
}
func BenchmarkSetSeq1e3(b *testing.B) {
benchmarkSetSeq(b, 1e3)
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment