Skip to content

Apply range-filters in get when probing active-listener cache #4408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/database/src/core/Repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,8 @@ export class Repo {
*/
getValue(query: Query): Promise<DataSnapshot> {
// Only active queries are cached. There is no persisted cache.
const cached = this.serverSyncTree_.calcCompleteEventCache(query.path);
if (!cached.isEmpty()) {
const cached = this.serverSyncTree_.getServerValue(query);
if (cached != null /*&& !cached.isEmpty()*/) {
return Promise.resolve(
new DataSnapshot(
cached,
Expand Down
47 changes: 32 additions & 15 deletions packages/database/src/core/SyncPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,15 @@ export class SyncPoint {
}
}

/**
* Add an event callback for the specified query.
*
* @param {!Query} query
* @param {!EventRegistration} eventRegistration
* @param {!WriteTreeRef} writesCache
* @param {?Node} serverCache Complete server cache, if we have it.
* @param {boolean} serverCacheComplete
* @return {!Array.<!Event>} Events to raise.
*/
addEventRegistration(
getView(
query: Query,
eventRegistration: EventRegistration,
writesCache: WriteTreeRef,
serverCache: Node | null,
serverCacheComplete: boolean
): Event[] {
): View {
const queryId = query.queryIdentifier();
let view = this.views.get(queryId);
console.log('We already have a view? ' + JSON.stringify(view));
if (!view) {
// TODO: make writesCache take flag for complete server node
let eventCache = writesCache.calcCompleteEventCache(
Expand Down Expand Up @@ -140,10 +130,37 @@ export class SyncPoint {
false
)
);
view = new View(query, viewCache);
this.views.set(queryId, view);
return new View(query, viewCache);
}
return view;
}

/**
* Add an event callback for the specified query.
*
* @param {!Query} query
* @param {!EventRegistration} eventRegistration
* @param {!WriteTreeRef} writesCache
* @param {?Node} serverCache Complete server cache, if we have it.
* @param {boolean} serverCacheComplete
* @return {!Array.<!Event>} Events to raise.
*/
addEventRegistration(
query: Query,
eventRegistration: EventRegistration,
writesCache: WriteTreeRef,
serverCache: Node | null,
serverCacheComplete: boolean
): Event[] {
let view = this.getView(
query,
writesCache,
serverCache,
serverCacheComplete
);
if (!this.views.has(query.queryIdentifier())) {
this.views.set(query.queryIdentifier(), view);
}
// This is guaranteed to exist now, we just created anything that was missing
view.addEventRegistration(eventRegistration);
return view.getInitialEvents(eventRegistration);
Expand Down
37 changes: 37 additions & 0 deletions packages/database/src/core/SyncTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Node } from './snap/Node';
import { Event } from './view/Event';
import { EventRegistration } from './view/EventRegistration';
import { View } from './view/View';
import { CacheNode } from './view/CacheNode';

/**
* @typedef {{
Expand Down Expand Up @@ -505,6 +506,42 @@ export class SyncTree {
);
}

getServerValue(query: Query): Node | null {
const path = query.path;
let serverCache: Node | null = null;
let foundAncestorDefaultView = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foundAncestorDefaultView is not used and can be removed.

// Any covering writes will necessarily be at the root, so really all we need to find is the server cache.
// Consider optimizing this once there's a better understanding of what actual behavior will be.
this.syncPointTree_.foreachOnPath(path, (pathToSyncPoint, sp) => {
const relativePath = Path.relativePath(pathToSyncPoint, path);
serverCache = serverCache || sp.getCompleteServerCache(relativePath);
foundAncestorDefaultView =
foundAncestorDefaultView || sp.hasCompleteView();
});
let syncPoint = this.syncPointTree_.get(path);
if (!syncPoint) {
syncPoint = new SyncPoint();
this.syncPointTree_ = this.syncPointTree_.set(path, syncPoint);
} else {
foundAncestorDefaultView =
foundAncestorDefaultView || syncPoint.hasCompleteView();
serverCache = serverCache || syncPoint.getCompleteServerCache(Path.Empty);
}
if (serverCache != null) {
let serverCacheNode: CacheNode | null = new CacheNode(
serverCache,
true,
false
);
let writesCache: WriteTreeRef | null = this.pendingWriteTree_.childWrites(
query.path
);
let view: View = syncPoint.getView(query, writesCache, serverCache, true);
return view.getCompleteServerCache(query.path);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schmidt-sebastian This seems to apply the filters for startAfter/endBefore, but it erroneously returns null for this test: https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/test/query.test.ts#L3159-L3179.

Can you have a look at this? I'm trying to figure out why this is happening but not making much progress.

}
return null;
}

/**
* This collapses multiple unfiltered views into a single view, since we only need a single
* listener for them.
Expand Down
1 change: 1 addition & 0 deletions packages/database/src/core/view/View.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class View {
this.query_.getQueryParams().loadsAllData() ||
(!path.isEmpty() && !cache.getImmediateChild(path.getFront()).isEmpty())
) {
console.log('Returning ' + cache.getChild(path));
return cache.getChild(path);
}
}
Expand Down
32 changes: 32 additions & 0 deletions packages/database/test/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,38 @@ describe('Query Tests', () => {
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childOne.key]]);
});

it('Ensure startAfter on key index works with overlapping listener', async () => {
const node = getRandomNode() as Reference;
const childOne = node.push();
const childTwo = node.push();
await childOne.set(1);
await childTwo.set(2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the tests still pass if you don't await here? I would like to confirm that we correctly apply local writes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They do still pass.

const ea = EventAccumulatorFactory.waitsForCount(1);
node.on('value', snap => {
ea.addEvent(snap.val());
});
await ea.promise;
const snap = await node.orderByKey().startAfter(childOne.key).get();
expect(Object.keys(snap.val())).to.deep.equal([childTwo.key]);
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childTwo.key]]);
});

it('Ensure endBefore on key index works with overlapping listener', async () => {
const node = getRandomNode() as Reference;
const childOne = node.push();
const childTwo = node.push();
await childOne.set(1);
await childTwo.set(2);
const ea = EventAccumulatorFactory.waitsForCount(1);
node.on('value', snap => {
ea.addEvent(snap.val());
});
await ea.promise;
const snap = await node.orderByKey().endBefore(childTwo.key).get();
expect(Object.keys(snap.val())).to.deep.equal([childOne.key]);
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childOne.key]]);
});

it('Ensure startAt / endAt with priority works.', async () => {
const node = getRandomNode() as Reference;

Expand Down