private fullLimitUpdateChild_()

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;
    }
  }