Skip to content

Commit

Permalink
Merge pull request #226 from appy-one/fix/issue-225
Browse files Browse the repository at this point in the history
Fix issue #225
  • Loading branch information
appy-one authored May 18, 2023
2 parents cd2ea57 + fa79239 commit c7d48c2
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ These events are mainly used by AceBase behind the scenes to automatically updat
Having said that, here's how to use them:
If we you want to monitor a specific node's value, but don't want to get its entire new value every time a small mutation is made to it, subscribe to the "mutated" event. This event is only fired with the target data actually being changed. This allows you to keep a cached copy of your data in memory (or cache db), and replicate all changes being made to it:
If you want to monitor a specific node's value, but don't want to get its entire new value every time a small mutation is made to it, subscribe to the "mutated" event. This event is only fired with the target data actually being changed. This allows you to keep a cached copy of your data in memory (or cache db), and replicate all changes being made to it:
```javascript
const chatRef = db.ref('chats/chat_id');
Expand Down
11 changes: 6 additions & 5 deletions src/btree/binary-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,8 @@ export class BinaryBPlusTree {
});

// update ext_block_free_length:
writeByteLength(bytes, 4, self._length - bytes.length);
const freeBytes = self._length - bytes.length + 8; // Do not count 8 header bytes
writeByteLength(bytes, 4, freeBytes);

const valueListLengthData = writeByteLength([], 0, self.totalValues - 1);
await Promise.all([
Expand All @@ -722,7 +723,7 @@ export class BinaryBPlusTree {
]);

self.totalValues--;
self._freeBytes = self._length - bytes.length;
self._freeBytes = freeBytes;
},
};

Expand Down Expand Up @@ -914,7 +915,7 @@ export class BinaryBPlusTree {
}
}

private async _writeLeaf(leafInfo: BinaryBPlusTreeLeaf): Promise<unknown[]> {
private async _writeLeaf(leafInfo: BinaryBPlusTreeLeaf, options: { addFreeSpace?: boolean } = { addFreeSpace: true }): Promise<unknown[]> {
assert(leafInfo.entries.every((entry, index, arr) => index === 0 || _isMore(entry.key, arr[index-1].key)), 'Leaf entries are not sorted ok');

try {
Expand All @@ -935,7 +936,7 @@ export class BinaryBPlusTree {
rebuild: leafInfo.extData.loaded,
}
: null;
const addFreeSpace = true;
const addFreeSpace = options.addFreeSpace !== false;
const writes = [];
const bytes = builder.createLeaf({
index: leafInfo.index,
Expand Down Expand Up @@ -2078,7 +2079,7 @@ export class BinaryBPlusTree {
// release allocated space again
if (oneLeafTree) {
if (options.rollbackOnFailure === false) { return; }
return this._writeLeaf(leaf);
return this._writeLeaf(leaf, { addFreeSpace: false });
}
else {
return this._registerFreeSpace(allocated.index, allocated.length);
Expand Down
16 changes: 13 additions & 3 deletions src/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export class Storage extends SimpleEventEmitter {
} // end of constructor

private _indexes: DataIndex[] = [];
private _annoucedIndexes = new Map<string, Promise<DataIndex>>();
public indexes = {
/**
* Tests if (the default storage implementation of) indexes are supported in the environment.
Expand Down Expand Up @@ -318,9 +319,18 @@ export class Storage extends SimpleEventEmitter {
if (existingIndex) {
return existingIndex;
}
else if (this._annoucedIndexes.has(fileName)) {
// Index is already in the process of being added, wait until it becomes availabe
const index = await this._annoucedIndexes.get(fileName);
return index;
}
try {
const index = await DataIndex.readFromFile(this, fileName);
// Announce the index to prevent race condition in between reading and receiving the IPC index.created notification
const indexPromise = DataIndex.readFromFile(this, fileName);
this._annoucedIndexes.set(fileName, indexPromise);
const index = await indexPromise;
this._indexes.push(index);
this._annoucedIndexes.delete(fileName);
return index;
}
catch(err) {
Expand Down Expand Up @@ -951,6 +961,7 @@ export class Storage extends SimpleEventEmitter {
else if (type === 'child_removed') {
trigger = oldValue !== null && newValue === null;
}
if (!trigger) { return; }

const pathKeys = PathInfo.getPathKeys(sub.dataPath);
variables.forEach(variable => {
Expand All @@ -960,7 +971,7 @@ export class Storage extends SimpleEventEmitter {
pathKeys[index] = variable.value;
});
const dataPath = pathKeys.reduce<string>((path, key) => PathInfo.getChildPath(path, key), '');
trigger && this.subscriptions.trigger(sub.type, sub.subscriptionPath, dataPath, oldValue, newValue, options.context);
this.subscriptions.trigger(sub.type, sub.subscriptionPath, dataPath, oldValue, newValue, options.context);
};

const prepareMutationEvents = (
Expand Down Expand Up @@ -1169,7 +1180,6 @@ export class Storage extends SimpleEventEmitter {
return result;
}


/**
* Enumerates all children of a given Node for reflection purposes
* @param path
Expand Down
103 changes: 103 additions & 0 deletions src/test/issue-225.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { AceBase } from '..';
import { createTempDB } from './tempdb';

describe('issue #225', () => {
let db: AceBase;
let removeDB: () => Promise<void>;

beforeAll(async () => {
({ db, removeDB } = await createTempDB({ logLevel: 'warn' }));
});

afterAll(async () => {
await removeDB();
});

it('realtime query and indexed deletes', async () => {
// Create indexes
await db.indexes.create('items', 'location');
await db.indexes.create('items', 'category');
await db.indexes.create('items', 'status');

const eventStats = { add: 0, change: 0, remove: 0 };

// Setup realtime query
const realtimeQuery = db.query('items')
.filter('category', '==', 'main')
.filter('status', '==', 1)
.on('add', event => {
eventStats.add++;
const item = event.snapshot.val();
console.log(`Added item ${event.ref.key}:`, item);
})
.on('change', event => {
eventStats.change++;
const item = event.snapshot.val();
console.log(`Changed item ${event.ref.key}:`, item);
})
.on('remove', event => {
eventStats.remove++;
console.log(`Removed item ${event.ref.key}`);
});

// Get initial (no) results
const results = await realtimeQuery.get();

// Add a bunch of items, should trigger "add" events on realtime query
const TEST_SIZE = 1000;
const itemIds = [] as string[];
const locations = ['Amsterdam', 'Cape Town', 'Sydney', 'Miami', 'Toronto', 'Berlin', 'Paris'];
for (let i = 0; i < TEST_SIZE; i++) {
const ref = await db.ref('items').push({
location: locations[Math.floor(Math.random() * locations.length)],
category: 'main',
status: 1,
});
itemIds.push(ref.key);
}

// Update every other item to status 2, should trigger 500 "remove" events on realtime query
for (let i = 0; i < itemIds.length; i += 2) {
await db.ref('items').child(itemIds[i]).update({
status: 2,
});
}

// Update every 3rd item to location 'Amsterdam', should trigger "change" events on realtime query
for (let i = 0; i < itemIds.length; i += 3) {
await db.ref('items').child(itemIds[i]).update({
location: 'Amsterdam',
});
}

// Update every 3rd item to status 3, should trigger some more "remove" events on realtime query
for (let i = 0; i < itemIds.length; i += 3) {
await db.ref('items').child(itemIds[i]).update({
status: 3,
});
}

// Remove items with status 1, should trigger all remaining "remove" events on realtime query (0 results left)
await db.query('items').filter('status', '==', 1).remove();

// Wait for all remove events to fire
console.log('Waiting for all remove events to fire');
await new Promise<void>(resolve => {
let eventsFired = eventStats.remove;
const check = () => {
if (eventsFired === eventStats.remove) {
// All fired
return resolve();
}
eventsFired = eventStats.remove;
setTimeout(check, 100); // Schedule next check
};
setTimeout(check, 1000); // Schedule first check
});

// Stop live query
await realtimeQuery.stop();

console.log(`${eventStats.add} added, ${eventStats.change} changed, ${eventStats.remove} removed`);
}, 600_000);
});

0 comments on commit c7d48c2

Please sign in to comment.