Merge pull request #240 from appy-one/fix/issue-239

Fix issue #239
This commit is contained in:
Ewout Stortenbeker 2023-08-16 23:33:05 +02:00 committed by GitHub
commit fc64f6b34d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 10 deletions

View file

@ -1758,9 +1758,17 @@ export class BinaryBPlusTree {
? results.filter(r => options.stats ? r.totalValues > 0 : r.value !== null)
: results;
}
const results = [] as Array<{ key: NodeEntryKeyType, value: any, totalValues: number }>;
// Get upperbound
const lastLeaf = await this._getLastLeaf();
let lastLeaf = await this._getLastLeaf();
while (lastLeaf.entries.length === 0 && lastLeaf.hasPrevious) {
lastLeaf = await lastLeaf.getPrevious();
}
if (lastLeaf.entries.length === 0) {
// Empty tree
return [] as typeof results;
}
const lastEntry = lastLeaf.entries.slice(-1)[0];
const lastKey = lastEntry.key;
@ -1773,7 +1781,10 @@ export class BinaryBPlusTree {
}
// Get lowerbound
const firstLeaf = await this._getFirstLeaf();
let firstLeaf = await this._getFirstLeaf();
while (firstLeaf.entries.length === 0 && firstLeaf.hasNext) {
firstLeaf = await firstLeaf.getNext();
}
const firstEntry = firstLeaf.entries[0];
const firstKey = firstEntry.key;
@ -1783,7 +1794,7 @@ export class BinaryBPlusTree {
}
// Some keys might be out of bounds, others must be looked up
const results = [] as Array<{ key: NodeEntryKeyType, value: any, totalValues: number }>, lookups = [] as NodeEntryKeyType[];
const lookups = [] as NodeEntryKeyType[];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (_isLess(key, firstKey) || _isMore(key, lastKey)) {
@ -2847,8 +2858,17 @@ export class BinaryBPlusTree {
else {
// Parent node has only 1 entry, removing it would also make parent node empty...
// throw new DetailedError('leaf-empty', 'leaf is now empty and parent node has only 1 entry, tree will have to be rebuilt');
// Write the empty leaf anyway, will be removed automatically upon a future tree rebuild.
// Write the empty leaf anyway, will be removed automatically with a tree rebuild.
await this._writeLeaf(leaf);
// Rebuild the tree
const options: Parameters<typeof this._rebuild>[1] = {
allocatedBytes: this.info.byteLength,
fillFactor: this.info.fillFactor,
increaseMaxEntries: false,
};
await this._rebuild(BinaryWriter.forFunction(this._writeFn), options);
await this._loadInfo(); // reload info
}
};
@ -2881,10 +2901,7 @@ export class BinaryBPlusTree {
return pointsThisDirection(node.parentNode);
}
else {
// There is no parent, this is the gtChild
if (!_isMoreOrEqual(key, node.entries.slice(-1)[0].key)) {
throw new Error('DEV ERROR: this tree is not right..');
}
// There is no parent, this is a single-leaf tree
return true;
}
};
@ -3000,7 +3017,7 @@ export class BinaryBPlusTree {
throw new DetailedError('small-ptrs-deprecated', 'small ptrs have deprecated, tree will have to be rebuilt');
}
const entryIndex = leaf.entries.findIndex(entry => _isEqual(key, entry.key));
if (!~entryIndex) { return; }
if (entryIndex < 0) { return; }
if (this.info.isUnique || typeof recordPointer === 'undefined' || leaf.entries[entryIndex].totalValues === 1) {
leaf.entries.splice(entryIndex, 1);
}
@ -3009,7 +3026,7 @@ export class BinaryBPlusTree {
}
else {
const valueIndex = leaf.entries[entryIndex].values.findIndex(val => _compareBinary(val.recordPointer, recordPointer));
if (!~valueIndex) { return; }
if (valueIndex < 0) { return; }
leaf.entries[entryIndex].values.splice(valueIndex, 1);
}
if (leaf.parentNode && leaf.entries.length === 0) {

View file

@ -0,0 +1,38 @@
import { AceBase } from '../..';
import { createTempDB } from '../../test/tempdb';
describe('issue', () => {
let db: AceBase, removeDB: () => Promise<void>;
beforeAll(async ()=> {
({ db, removeDB } = await createTempDB({ config(options) {
options.logLevel = 'warn';
}}));
});
afterAll(async () => {
await removeDB();
});
it('#239', async () => {
// Created for issue #239 ("TypeError when trying to add new records after removing old ones")
const ref = db.ref('table');
// add "large" dataset to the database
const songIds1 = Array(110).fill(0).map((_value, index) => `id-${index}`);
await ref.update(songIds1.reduce((obj, songId) => {
obj[songId] = { playlistId: 'playlist1' };
return obj;
}, {} as any));
// remove all the added records with the query
await ref.query().filter('playlistId', '==', 'playlist1').remove();
// add new "small" dataset to the database -> error
const songIds2 = Array(10).fill(0).map((_value, index) => `id-${index}`);
await ref.update(songIds2.reduce((obj, songId) => {
obj[songId] = { playlistId: 'playlist1' };
return obj;
}, {} as any));
});
});