class Google::Cloud::Firestore::Watch::Inventory

@private Collects changes and produces a QuerySnapshot. Uses RBTree to hold a sorted list of DocumentSnapshot objects and to make inserting and removing objects much more efficent.

Attributes

current[RW]
read_time[R]
resume_token[R]

Public Class Methods

new(client, query) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 39
def initialize client, query
  @client = client
  @query = query
  @pending = {
    add:    [],
    delete: []
  }
  @current = nil
  @resume_token = nil
  @read_time = nil
  @tree = RBTree.new
  @tree.readjust(&method(:query_comparison_proc))
  @old_order = nil

  # TODO: Remove this when done benchmarking
  @comp_proc_counter = 0
end

Public Instance Methods

add(doc_grpc) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 61
def add doc_grpc
  @pending[:add] << doc_grpc
end
build_query_snapshot() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 144
def build_query_snapshot
  # If this is the first time building, set to empty hash
  @old_order ||= {}

  # Get the new set of documents, changes, order
  docs = current_docs
  new_order = order_for docs
  changes = build_changes new_order, @old_order
  @old_order = new_order

  QuerySnapshot.from_docs @query, docs, changes, @read_time
end
changes?() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 122
def changes?
  # Act like there are changes if we have never run before
  return true if @old_order.nil?
  added_paths, deleted_paths, changed_paths = \
    change_paths current_order, @old_order
  added_paths.any? || deleted_paths.any? || changed_paths.any?
end
clear_pending() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 73
def clear_pending
  @pending[:add].clear
  @pending[:delete].clear
end
count()
Alias for: size
count_with_pending()
Alias for: size_with_pending
current?() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 57
def current?
  @current
end
current_docs() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 130
def current_docs
  # Reverse the results for Query#limit_to_last queries since that method reversed the order_by directions.
  return @tree.keys.reverse if @query&.limit_type == :last
  @tree.keys
end
current_order() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 140
def current_order
  order_for current_docs
end
delete(doc_path) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 65
def delete doc_path
  @pending[:delete] << doc_path
end
order_for(docs) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 136
def order_for docs
  Hash[docs.map { |doc| [doc.path, doc.updated_at] }]
end
pending?() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 69
def pending?
  @pending[:add].any? || @pending[:delete].any?
end
persist(resume_token, read_time) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 114
def persist resume_token, read_time
  @resume_token = resume_token
  @read_time = read_time

  apply_pending_changes_to_tree @pending, @tree
  clear_pending
end
reset() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 99
def reset
  restart

  # clears the resume token and read time, but not query and old order
  @resume_token = nil
  @read_time = nil
end
reset_comp_proc_counter!() click to toggle source

TODO: Remove this when done benchmarking

# File lib/google/cloud/firestore/watch/inventory.rb, line 108
def reset_comp_proc_counter!
  old_count = @comp_proc_counter
  @comp_proc_counter = 0
  old_count
end
restart() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 90
def restart
  # clears all but query, resume token, read time, and old order
  clear_pending

  @current = nil

  @tree.clear
end
size() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 78
def size
  @tree.size
end
Also aliased as: count
size_with_pending() click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 83
def size_with_pending
  count_with_pending_tree = @tree.dup
  apply_pending_changes_to_tree @pending, count_with_pending_tree
  count_with_pending_tree.size
end
Also aliased as: count_with_pending

Protected Instance Methods

add_doc_to_tree(doc_snp, tree) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 263
def add_doc_to_tree doc_snp, tree
  tree[doc_snp] = doc_snp.path
end
apply_pending_changes_to_tree(pending, tree) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 181
def apply_pending_changes_to_tree pending, tree
  # Remove the deleted documents
  pending[:delete].each do |doc_path|
    remove_doc_from_tree doc_path, tree
  end

  # Add/update the changed documents
  pending[:add].each do |doc_grpc|
    removed_doc = remove_doc_from_tree doc_grpc.name, tree
    added_doc = DocumentSnapshot.from_document(
      doc_grpc, @client, read_at: read_time
    )

    if removed_doc && removed_doc.updated_at >= added_doc.updated_at
      # Restore the removed doc if the added doc isn't newer
      added_doc = removed_doc
    end

    add_doc_to_tree added_doc, tree
  end
end
build_added_doc_change(doc_path, new_paths) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 242
def build_added_doc_change doc_path, new_paths
  doc_snp = get_doc_from_tree doc_path, @tree
  new_index = get_index_from_order_array doc_path, new_paths
  DocumentChange.from_doc doc_snp, nil, new_index
end
build_changes(new_order, old_order) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 217
def build_changes new_order, old_order
  new_paths = new_order.keys
  old_paths = old_order.keys
  added_paths, deleted_paths, changed_paths = \
    change_paths new_order, old_order

  changes = deleted_paths.map do |doc_path|
    build_deleted_doc_change doc_path, old_paths
  end
  changes += added_paths.map do |doc_path|
    build_added_doc_change doc_path, new_paths
  end
  changes += changed_paths.map do |doc_path|
    build_modified_doc_change doc_path, new_paths, old_paths
  end
  changes
end
build_deleted_doc_change(doc_path, old_paths) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 235
def build_deleted_doc_change doc_path, old_paths
  doc_ref = DocumentReference.from_path doc_path, @client
  doc_snp = DocumentSnapshot.missing doc_ref
  old_index = get_index_from_order_array doc_path, old_paths
  DocumentChange.from_doc doc_snp, old_index, nil
end
build_modified_doc_change(doc_path, new_paths, old_paths) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 248
def build_modified_doc_change doc_path, new_paths, old_paths
  doc_snp = get_doc_from_tree doc_path, @tree
  old_index = get_index_from_order_array doc_path, old_paths
  new_index = get_index_from_order_array doc_path, new_paths
  DocumentChange.from_doc doc_snp, old_index, new_index
end
change_paths(new_order, old_order) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 203
def change_paths new_order, old_order
  added_paths = new_order.keys - old_order.keys
  deleted_paths = old_order.keys - new_order.keys
  new_hash = new_order.dup.delete_if do |path, _updated_at|
    added_paths.include? path
  end
  old_hash = old_order.dup.delete_if do |path, _updated_at|
    deleted_paths.include? path
  end
  changed_paths = (new_hash.to_a - old_hash.to_a).map(&:first)

  [added_paths, deleted_paths, changed_paths]
end
get_doc_from_tree(doc_path, tree) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 259
def get_doc_from_tree doc_path, tree
  tree.key doc_path
end
get_index_from_order_array(doc_path, order_array) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 255
def get_index_from_order_array doc_path, order_array
  order_array.index doc_path
end
query_comparison_proc(left, right) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 159
def query_comparison_proc left, right
  # TODO: Remove this when done benchmarking
  @comp_proc_counter += 1

  return Order.compare_field_values left.ref, right.ref if @query.nil?

  @directions ||= @query.query.order_by.map(&:direction)

  left_comps = left.query_comparisons_for @query.query
  right_comps = right.query_comparisons_for @query.query
  @directions.zip(left_comps, right_comps).each do |dir, left_comp, right_comp|
    comp = left_comp <=> right_comp
    comp = 0 - comp if dir == :DESCENDING
    return comp unless comp.zero?
  end

  # Compare paths when everything else is equal
  ref_comp = Order.compare_field_values left.ref, right.ref
  ref_comp = 0 - ref_comp if @directions.last == :DESCENDING
  ref_comp
end
remove_doc_from_tree(doc_path, tree) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 267
def remove_doc_from_tree doc_path, tree
  # Remove old snapshot
  old_snp = tree.key doc_path
  tree.delete old_snp unless old_snp.nil?
  old_snp
end
type_from_indexes(old_index, new_index) click to toggle source
# File lib/google/cloud/firestore/watch/inventory.rb, line 274
def type_from_indexes old_index, new_index
  return :removed if new_index.nil?
  return :added if old_index.nil?
  :modified
end