class NoSE::Index

A representation of materialized views over fields in an entity

Attributes

all_fields[R]
entries[R]
entry_size[R]
extra[R]
graph[R]
hash_count[R]
hash_fields[R]
order_fields[R]
path[R]
per_hash_count[R]
size[R]

Public Class Methods

new(hash_fields, order_fields, extra, graph, saved_key: nil) click to toggle source
# File lib/nose/indexes.rb, line 10
def initialize(hash_fields, order_fields, extra, graph,
               saved_key: nil)
  order_set = order_fields.to_set
  @hash_fields = hash_fields.to_set
  @order_fields = order_fields.delete_if { |e| hash_fields.include? e }
  @extra = extra.to_set.delete_if do |e|
    @hash_fields.include?(e) || order_set.include?(e)
  end
  @all_fields = Set.new(@hash_fields).merge(order_set).merge(@extra)

  validate_hash_fields

  # Store whether this index is an identity
  @identity = @hash_fields == [
    @hash_fields.first.parent.id_field
  ].to_set && graph.nodes.size == 1

  @graph = graph
  @path = graph.longest_path
  @path = nil unless @path.length == graph.size

  validate_graph

  build_hash saved_key
end

Public Instance Methods

==(other) click to toggle source

Two indices are equal if they contain the same fields @return [Boolean]

# File lib/nose/indexes.rb, line 88
def ==(other)
  hash == other.hash
end
Also aliased as: eql?
[](field_id) click to toggle source

Look up a field in the index based on its ID @return [Fields::Field]

# File lib/nose/indexes.rb, line 50
def [](field_id)
  @all_fields.find { |field| field.id == field_id }
end
contains_field?(field) click to toggle source

Check if the index contains a given field @return [Boolean]

# File lib/nose/indexes.rb, line 110
def contains_field?(field)
  @all_fields.include? field
end
eql?(other)
Alias for: ==
hash() click to toggle source
# File lib/nose/indexes.rb, line 104
def hash
  @hash ||= Zlib.crc32 hash_str
end
hash_str() click to toggle source

Hash based on the fields, their keys, and the graph @return [String]

# File lib/nose/indexes.rb, line 95
def hash_str
  @hash_str ||= [
    @hash_fields.map(&:id).sort!,
    @order_fields.map(&:id),
    @extra.map(&:id).sort!,
    @graph.unique_edges.map(&:canonical_params).sort!
  ].to_s.freeze
end
id_graph?() click to toggle source

Check if this index is an ID graph @return [Boolean]

# File lib/nose/indexes.rb, line 56
def id_graph?
  @hash_fields.all?(&:primary_key?) && @order_fields.all?(&:primary_key)
end
identity?() click to toggle source

Check if this index maps from the primary key to fields from one entity @return [Boolean]

# File lib/nose/indexes.rb, line 38
def identity?
  @identity
end
key() click to toggle source

A simple key which uniquely identifies the index @return [String]

# File lib/nose/indexes.rb, line 44
def key
  @key ||= "i#{Zlib.crc32 hash_str}"
end
to_color() click to toggle source

:nocov:

# File lib/nose/indexes.rb, line 75
def to_color
  fields = [@hash_fields, @order_fields, @extra].map do |field_group|
    '[' + field_group.map(&:inspect).join(', ') + ']'
  end

  "[magenta]#{key}[/] #{fields[0]} #{fields[1]} → #{fields[2]}" \
    " [yellow]$#{size}[/]" \
    " [magenta]#{@graph.inspect}[/]"
end
to_id_graph() click to toggle source

Produce an index with the same fields but keyed by entities in the graph

# File lib/nose/indexes.rb, line 61
def to_id_graph
  return self if id_graph?

  all_ids = (@hash_fields.to_a + @order_fields + @extra.to_a)
  all_ids.map! { |f| f.parent.id_field }.uniq!

  hash_fields = [all_ids.first]
  order_fields = all_ids[1..-1]
  extra = @all_fields - hash_fields - order_fields

  Index.new hash_fields, order_fields, extra, @graph
end

Private Instance Methods

build_hash(saved_key) click to toggle source

Initialize the hash function and freeze ourselves @return [void]

# File lib/nose/indexes.rb, line 118
def build_hash(saved_key)
  @key = saved_key

  hash
  key
  calculate_size
  freeze
end
calculate_size() click to toggle source

Precalculate the size of the index @return [void]

# File lib/nose/indexes.rb, line 170
def calculate_size
  @hash_count = @hash_fields.product_by(&:cardinality)

  # XXX This only works if foreign keys span all possible keys
  #     Take the maximum possible count at each join and multiply
  @entries = @graph.entities.map(&:count).max
  @per_hash_count = (@entries * 1.0 / @hash_count)

  @entry_size = @all_fields.sum_by(&:size)
  @size = @entries * @entry_size
end
validate_graph() click to toggle source

Ensure an index and its fields correspond to a valid graph @return [void]

# File lib/nose/indexes.rb, line 146
def validate_graph
  validate_graph_entities
  validate_graph_keys
end
validate_graph_entities() click to toggle source

Ensure the graph of the index is valid @return [void]

# File lib/nose/indexes.rb, line 153
def validate_graph_entities
  entities = @all_fields.map(&:parent).to_set
  fail InvalidIndexException, 'graph entities do match index' \
    unless entities == @graph.entities.to_set
end
validate_graph_keys() click to toggle source

We must have the primary keys of the all entities in the graph @return [void]

# File lib/nose/indexes.rb, line 161
def validate_graph_keys
  fail InvalidIndexException, 'missing graph entity keys' \
    unless @graph.entities.map(&:id_field).all? do |field|
      @hash_fields.include?(field) || @order_fields.include?(field)
    end
end
validate_hash_fields() click to toggle source

Check for valid hash fields in an index @return [void]

# File lib/nose/indexes.rb, line 129
def validate_hash_fields
  fail InvalidIndexException, 'hash fields cannot be empty' \
    if @hash_fields.empty?

  fail InvalidIndexException, 'hash fields can only involve one entity' \
    if @hash_fields.map(&:parent).to_set.size > 1
end
validate_nonempty() click to toggle source

Ensure an index is nonempty @return [void]

# File lib/nose/indexes.rb, line 139
def validate_nonempty
  fail InvalidIndexException, 'must have fields other than hash fields' \
    if @order_fields.empty? && @extra.empty?
end