# All files / local reference_set.ts

95.45% Statements 63/66
100% Branches 8/8
90.48% Functions 19/21
98.25% Lines 56/57

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156

    2x
    2x
    2x
    2x

    2x

    2x

    1673x

    1673x

    1673x

    2x
    17x

    2x
    863x
    863x
    863x

    1110x
    1110x

    2x
    167x

    1090x
    1090x

    751x
    751x
    751x
    751x
    751x
    470x

    2x

    2x
    637x
    637x
    637x
    509x

    2x
    233x
    233x
    233x
    233x
    233x
    20x

    233x

    2x
    1382x

    2x

    1492x
    1492x
    1492x

    2x

    2x

    8311x
    8311x

    2x
    10165x

    2x
    5315x

    2x
     /**
     * Copyright 2017 Google Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    import { BatchId, TargetId } from '../core/types';
    import { documentKeySet, DocumentKeySet } from '../model/collections';
    import { DocumentKey } from '../model/document_key';
    import { primitiveComparator } from '../util/misc';
    import { SortedSet } from '../util/sorted_set';

    import { GarbageCollector } from './garbage_collector';
    import { GarbageSource } from './garbage_source';
    import { PersistenceTransaction } from './persistence';
    import { PersistencePromise } from './persistence_promise';

    /**
     * A collection of references to a document from some kind of numbered entity
     * (either a target ID or batch ID). As references are added to or removed from
     * the set corresponding events are emitted to a registered garbage collector.
     *
     * Each reference is represented by a DocumentReference object. Each of them
     * contains enough information to uniquely identify the reference. They are all
     * stored primarily in a set sorted by key. A document is considered garbage if
     * there's no references in that set (this can be efficiently checked thanks to
     * sorting by key).
     *
     * ReferenceSet also keeps a secondary set that contains references sorted by
     * IDs. This one is used to efficiently implement removal of all references by
     * some target ID.
     */
    export class ReferenceSet implements GarbageSource {
      // A set of outstanding references to a document sorted by key.
      private refsByKey = new SortedSet(DocReference.compareByKey);

      // A set of outstanding references to a document sorted by target id.
      private refsByTarget = new SortedSet(DocReference.compareByTargetId);

      /** Keeps track of keys that have references */
      private garbageCollector: GarbageCollector | null = null;

      /** Returns true if the reference set contains no references. */
      isEmpty(): boolean {
        return this.refsByKey.isEmpty();
      }

      /** Adds a reference to the given document key for the given ID. */
      addReference(key: DocumentKey, id: TargetId | BatchId): void {
        const ref = new DocReference(key, id);
        this.refsByKey = this.refsByKey.add(ref);
        this.refsByTarget = this.refsByTarget.add(ref);
      }

      /** Add references to the given document keys for the given ID. */
      addReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
        keys.forEach(key => this.addReference(key, id));
      }

      /**
       * Removes a reference to the given document key for the given
       * ID.
       */
      removeReference(key: DocumentKey, id: TargetId | BatchId): void {
        this.removeRef(new DocReference(key, id));
      }

      removeReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
        keys.forEach(key => this.removeReference(key, id));
      }

      /**
       * Clears all references with a given ID. Calls removeRef() for each key
       * removed.
       */
      removeReferencesForId(id: TargetId | BatchId): void {
        const emptyKey = DocumentKey.EMPTY;
        const startRef = new DocReference(emptyKey, id);
        const endRef = new DocReference(emptyKey, id + 1);
        this.refsByTarget.forEachInRange([startRef, endRef], ref => {
          this.removeRef(ref);
        });
      }

      removeAllReferences(): void {
        this.refsByKey.forEach(ref => this.removeRef(ref));
      }

      private removeRef(ref: DocReference): void {
        this.refsByKey = this.refsByKey.delete(ref);
        this.refsByTarget = this.refsByTarget.delete(ref);
        if (this.garbageCollector !== null) {
          this.garbageCollector.addPotentialGarbageKey(ref.key);
        }
      }

      referencesForId(id: TargetId | BatchId): DocumentKeySet {
        const emptyKey = DocumentKey.EMPTY;
        const startRef = new DocReference(emptyKey, id);
        const endRef = new DocReference(emptyKey, id + 1);
        let keys = documentKeySet();
        this.refsByTarget.forEachInRange([startRef, endRef], ref => {
          keys = keys.add(ref.key);
        });
        return keys;
      }

      setGarbageCollector(garbageCollector: GarbageCollector | null): void {
        this.garbageCollector = garbageCollector;
      }

      containsKey(
        txn: PersistenceTransaction | null,
        key: DocumentKey
      ): PersistencePromise<boolean> {
        const ref = new DocReference(key, 0);
        const firstRef = this.refsByKey.firstAfterOrEqual(ref);
        return PersistencePromise.resolve(
          firstRef !== null && key.isEqual(firstRef.key)
        );
      }
    }

    export class DocReference {
      constructor(
        public key: DocumentKey,
        public targetOrBatchId: TargetId | BatchId
      ) {}

      /** Compare by key then by ID */
      static compareByKey(left: DocReference, right: DocReference): number {
        return (
          DocumentKey.comparator(left.key, right.key) ||
          primitiveComparator(left.targetOrBatchId, right.targetOrBatchId)
        );
      }

      /** Compare by ID then by key */
      static compareByTargetId(left: DocReference, right: DocReference): number {
        return (
          primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
          DocumentKey.comparator(left.key, right.key)
        );
      }
    }