in packages/database/src/core/view/filter/LimitedFilter.ts [201:302]
private fullLimitUpdateChild_(
snap: Node,
childKey: string,
childSnap: Node,
source: CompleteChildSource,
changeAccumulator: ChildChangeAccumulator | null
): Node {
// TODO: rename all cache stuff etc to general snap terminology
let cmp;
if (this.reverse_) {
const indexCmp = this.index_.getCompare();
cmp = (a: NamedNode, b: NamedNode) => indexCmp(b, a);
} else {
cmp = this.index_.getCompare();
}
const oldEventCache = snap as ChildrenNode;
assert(oldEventCache.numChildren() === this.limit_, '');
const newChildNamedNode = new NamedNode(childKey, childSnap);
const windowBoundary = this.reverse_
? oldEventCache.getFirstChild(this.index_)
: (oldEventCache.getLastChild(this.index_) as NamedNode);
const inRange = this.rangedFilter_.matches(newChildNamedNode);
if (oldEventCache.hasChild(childKey)) {
const oldChildSnap = oldEventCache.getImmediateChild(childKey);
let nextChild = source.getChildAfterChild(
this.index_,
windowBoundary,
this.reverse_
);
while (
nextChild != null &&
(nextChild.name === childKey || oldEventCache.hasChild(nextChild.name))
) {
// There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
// been applied to the limited filter yet. Ignore this next child which will be updated later in
// the limited filter...
nextChild = source.getChildAfterChild(
this.index_,
nextChild,
this.reverse_
);
}
const compareNext =
nextChild == null ? 1 : cmp(nextChild, newChildNamedNode);
const remainsInWindow =
inRange && !childSnap.isEmpty() && compareNext >= 0;
if (remainsInWindow) {
if (changeAccumulator != null) {
changeAccumulator.trackChildChange(
changeChildChanged(childKey, childSnap, oldChildSnap)
);
}
return oldEventCache.updateImmediateChild(childKey, childSnap);
} else {
if (changeAccumulator != null) {
changeAccumulator.trackChildChange(
changeChildRemoved(childKey, oldChildSnap)
);
}
const newEventCache = oldEventCache.updateImmediateChild(
childKey,
ChildrenNode.EMPTY_NODE
);
const nextChildInRange =
nextChild != null && this.rangedFilter_.matches(nextChild);
if (nextChildInRange) {
if (changeAccumulator != null) {
changeAccumulator.trackChildChange(
changeChildAdded(nextChild.name, nextChild.node)
);
}
return newEventCache.updateImmediateChild(
nextChild.name,
nextChild.node
);
} else {
return newEventCache;
}
}
} else if (childSnap.isEmpty()) {
// we're deleting a node, but it was not in the window, so ignore it
return snap;
} else if (inRange) {
if (cmp(windowBoundary, newChildNamedNode) >= 0) {
if (changeAccumulator != null) {
changeAccumulator.trackChildChange(
changeChildRemoved(windowBoundary.name, windowBoundary.node)
);
changeAccumulator.trackChildChange(
changeChildAdded(childKey, childSnap)
);
}
return oldEventCache
.updateImmediateChild(childKey, childSnap)
.updateImmediateChild(windowBoundary.name, ChildrenNode.EMPTY_NODE);
} else {
return snap;
}
} else {
return snap;
}
}