Skip to content

Commit 2a7c8d4

Browse files
authored
Merge pull request #30 from CraigMacomber/isSharedBug
Fix IsShared bug when editRange removes multiple layers
2 parents 7077bd1 + 465bbf4 commit 2a7c8d4

File tree

5 files changed

+66
-7
lines changed

5 files changed

+66
-7
lines changed

b+tree.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ var __extends = (this && this.__extends) || (function () {
33
var extendStatics = function (d, b) {
44
extendStatics = Object.setPrototypeOf ||
55
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6-
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
77
return extendStatics(d, b);
88
};
99
return function (d, b) {
10+
if (typeof b !== "function" && b !== null)
11+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
1012
extendStatics(d, b);
1113
function __() { this.constructor = d; }
1214
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
@@ -979,9 +981,16 @@ var BTree = /** @class */ (function () {
979981
return typeof r === "number" ? r : r.break;
980982
}
981983
finally {
982-
while (root.keys.length <= 1 && !root.isLeaf)
984+
var isShared = undefined;
985+
while (root.keys.length <= 1 && !root.isLeaf) {
986+
isShared || (isShared = root.isShared);
983987
this._root = root = root.keys.length === 0 ? EmptyLeaf :
984988
root.children[0];
989+
}
990+
// If any ancestor of the new root was shared, the new root must also be shared
991+
if (isShared) {
992+
root.isShared = true;
993+
}
985994
}
986995
};
987996
/** Same as `editRange` except that the callback is called for all pairs. */

b+tree.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,49 @@ describe('Simple tests on leaf nodes', () =>
371371
});
372372
});
373373

374+
// Tests relating to `isShared` and cloning.
375+
// Tests on this subject that do not care about the specific interior structure of the tree
376+
// (and are thus maxNodeSize agnostic) can be added to testBTree to be testing on different branching factors instead.
377+
describe("cloning and sharing tests", () => {
378+
test("Regression test for failing to propagate shared when removing top two layers", () => {
379+
// This tests make a full 3 layer tree (height = 2), so use a small branching factor.
380+
const maxNodeSize = 4;
381+
const tree = new BTree<number, number>(
382+
undefined,
383+
simpleComparator,
384+
maxNodeSize
385+
);
386+
// Build a 3 layer complete tree, all values 0.
387+
for (
388+
let index = 0;
389+
index < maxNodeSize * maxNodeSize * maxNodeSize;
390+
index++
391+
) {
392+
tree.set(index, 0);
393+
}
394+
// Leaf nodes don't count, so this is depth 2
395+
expect(tree.height).toBe(2);
396+
397+
// Edit the tree so it has a node in the second layer with exactly one child (key 0).
398+
tree.deleteRange(1, maxNodeSize * maxNodeSize, false);
399+
expect(tree.height).toBe(2);
400+
401+
// Make a clone that should never be mutated.
402+
const clone = tree.clone();
403+
404+
// Mutate the original tree in such a way that clone gets mutated due to incorrect is shared tracking.
405+
// Delete everything outside of the internal node with only one child, so its child becomes the new root.
406+
tree.deleteRange(maxNodeSize, Number.POSITIVE_INFINITY, false);
407+
expect(tree.height).toBe(0);
408+
409+
// Modify original
410+
tree.set(0, 1);
411+
412+
// Check that clone is not modified as well:
413+
expect(clone.get(0)).toBe(0);
414+
});
415+
});
416+
374417
describe('B+ tree with fanout 32', testBTree.bind(null, 32));
375418
describe('B+ tree with fanout 10', testBTree.bind(null, 10));
376419
describe('B+ tree with fanout 4', testBTree.bind(null, 4));

b+tree.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,9 +1094,16 @@ export default class BTree<K=any, V=any> implements ISortedMapF<K,V>, ISortedMap
10941094
var r = root.forRange(low, high, includeHigh, true, this, initialCounter || 0, onFound);
10951095
return typeof r === "number" ? r : r.break!;
10961096
} finally {
1097-
while (root.keys.length <= 1 && !root.isLeaf)
1097+
let isShared: undefined | true = undefined;
1098+
while (root.keys.length <= 1 && !root.isLeaf) {
1099+
isShared ||= root.isShared;
10981100
this._root = root = root.keys.length === 0 ? EmptyLeaf :
10991101
(root as any as BNodeInternal<K,V>).children[0];
1102+
}
1103+
// If any ancestor of the new root was shared, the new root must also be shared
1104+
if (isShared) {
1105+
root.isShared = true;
1106+
}
11001107
}
11011108
}
11021109

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"testpack-cli": "^1.1.4",
5959
"ts-jest": "^26.4.3",
6060
"ts-node": "^7.0.1",
61-
"typescript": "^3.8.3",
61+
"typescript": "^4.0.8",
6262
"uglify-js": "^3.11.4"
6363
},
6464
"dependencies": {},

0 commit comments

Comments
 (0)